Merge remote-tracking branch 'upstream/master' into test_short_dict
This commit is contained in:
commit
060a3998c6
@ -30,6 +30,8 @@ Added
|
||||
(to report bugs which are difficult to reproduce).
|
||||
- New `hide-unmatched-rapid-hints` option to not hide hint unmatched hint labels
|
||||
in rapid mode.
|
||||
- New `{clipboard}` and `{primary}` replacements for the commandline which
|
||||
replace the `:paste` command.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@ -58,11 +60,19 @@ Changed
|
||||
- With `new-instance-open-target` set to a tab option, the tab is now opened in
|
||||
the most recently focused (instead of the last opened) window. This can be
|
||||
configured with the new `new-instance-open-target.window` setting.
|
||||
It can also be set to `last-visible` to show the pages in the most recently
|
||||
visible window.
|
||||
- Word hints now are more clever about getting the element text from some elements.
|
||||
- Completions for `:help` and `:bind` now also show hidden commands
|
||||
- The `:buffer` completion now also filters using the first column (id).
|
||||
- `:undo` has been improved to reopen tabs at the position they were closed.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- The `:paste` command got deprecated as `:open` with `{clipboard}` and
|
||||
`{primary}` can be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
@ -79,6 +89,7 @@ Fixed
|
||||
- `:bind` can now be used to bind to an alias (binding by editing `keys.conf`
|
||||
already worked before)
|
||||
- The command completion now updates correctly when changing aliases
|
||||
- `:undo` now doesn't undo tabs "closed" by `:tab-detach` anymore.
|
||||
|
||||
v0.8.3 (unreleased)
|
||||
-------------------
|
||||
|
@ -145,12 +145,12 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Bruno Oliveira
|
||||
* Jan Verbeek
|
||||
* Alexander Cogneau
|
||||
* Marshall Lochbaum
|
||||
* Jakub Klinkovský
|
||||
* Felix Van der Jeugt
|
||||
* Martin Tournoij
|
||||
* Jan Verbeek
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Patric Schmitz
|
||||
@ -192,6 +192,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Nick Ginther
|
||||
* Michał Góral
|
||||
* Michael Ilsaas
|
||||
* Michael Hoang
|
||||
* Martin Zimmermann
|
||||
* Fritz Reichwald
|
||||
* Brian Jackson
|
||||
@ -213,7 +214,6 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* adam
|
||||
* Samir Benmendil
|
||||
* Regina Hug
|
||||
* Michael Hoang
|
||||
* Mathias Fussenegger
|
||||
* Marcelo Santos
|
||||
* Jean-Louis Fuchs
|
||||
|
@ -38,7 +38,6 @@
|
||||
|<<messages,messages>>|Show a log of past messages.
|
||||
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
||||
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||
|<<paste,paste>>|Open a page from the clipboard.
|
||||
|<<print,print>>|Print the current/[count]th tab.
|
||||
|<<quickmark-add,quickmark-add>>|Add a new quickmark.
|
||||
|<<quickmark-del,quickmark-del>>|Delete a quickmark.
|
||||
@ -487,6 +486,8 @@ Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] ['url']+
|
||||
|
||||
Open a URL in the current/[count]th tab.
|
||||
|
||||
If the URL contains newlines, each line gets opened in its own tab.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: The URL to open.
|
||||
|
||||
@ -503,20 +504,6 @@ The tab index to open the URL in.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[paste]]
|
||||
=== paste
|
||||
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Open a page from the clipboard.
|
||||
|
||||
If the pasted text contains newlines, each line gets opened in its own tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in new window.
|
||||
|
||||
[[print]]
|
||||
=== print
|
||||
Syntax: +:print [*--preview*] [*--pdf* 'file']+
|
||||
|
@ -456,6 +456,7 @@ Valid values:
|
||||
|
||||
* +last-opened+: Open new tabs in the last opened window.
|
||||
* +last-focused+: Open new tabs in the most recently focused window.
|
||||
* +last-visible+: Open new tabs in the most recently visible window.
|
||||
|
||||
Default: +pass:[last-focused]+
|
||||
|
||||
|
@ -350,6 +350,9 @@ def on_focus_changed(_old, new):
|
||||
window = new.window()
|
||||
if isinstance(window, mainwindow.MainWindow):
|
||||
objreg.register('last-focused-main-window', window, update=True)
|
||||
# A focused window must also be visible, and in this case we should
|
||||
# consider it as the most recently looked-at window
|
||||
objreg.register('last-visible-main-window', window, update=True)
|
||||
|
||||
|
||||
def open_desktopservices_url(url):
|
||||
|
@ -220,19 +220,6 @@ class AbstractZoom(QObject):
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
|
||||
@pyqtSlot(QPoint)
|
||||
def _on_mouse_wheel_zoom(self, delta):
|
||||
"""Handle zooming via mousewheel requested by the web view."""
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
factor = self.factor() + delta.y() / divider
|
||||
if factor < 0:
|
||||
return
|
||||
perc = int(100 * factor)
|
||||
message.info(self._win_id, "Zoom level: {}%".format(perc))
|
||||
self._neighborlist.fuzzyval = perc
|
||||
self._set_factor_internal(factor)
|
||||
self._default_zoom_changed = True
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
|
||||
@ -485,9 +472,6 @@ class AbstractTab(QWidget):
|
||||
self._mouse_event_filter = mouse.MouseEventFilter(self, parent=self)
|
||||
self.backend = None
|
||||
|
||||
def _event_filter_target(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
self._widget = widget
|
||||
@ -498,9 +482,10 @@ class AbstractTab(QWidget):
|
||||
self.zoom._widget = widget
|
||||
self.search._widget = widget
|
||||
self.printing._widget = widget
|
||||
widget.mouse_wheel_zoom.connect(self.zoom._on_mouse_wheel_zoom)
|
||||
event_filter_target = self._event_filter_target()
|
||||
event_filter_target.installEventFilter(self._mouse_event_filter)
|
||||
self._install_event_filter()
|
||||
|
||||
def _install_event_filter(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_load_status(self, val):
|
||||
"""Setter for load_status."""
|
||||
|
@ -236,6 +236,8 @@ class CommandDispatcher:
|
||||
bg=False, tab=False, window=False, count=None):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
|
||||
If the URL contains newlines, each line gets opened in its own tab.
|
||||
|
||||
Args:
|
||||
url: The URL to open.
|
||||
bg: Open in a new background tab.
|
||||
@ -247,35 +249,73 @@ class CommandDispatcher:
|
||||
"""
|
||||
if url is None:
|
||||
if tab or bg or window:
|
||||
url = config.get('general', 'default-page')
|
||||
urls = [config.get('general', 'default-page')]
|
||||
else:
|
||||
raise cmdexc.CommandError("No URL given, but -t/-b/-w is not "
|
||||
"set!")
|
||||
else:
|
||||
try:
|
||||
url = objreg.get('quickmark-manager').get(url)
|
||||
except urlmarks.Error:
|
||||
try:
|
||||
url = urlutils.fuzzy_url(url)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
# We don't use cmdexc.CommandError here as this can be
|
||||
# called async from edit_url
|
||||
message.error(self._win_id, str(e))
|
||||
return
|
||||
if tab or bg or window:
|
||||
self._open(url, tab, bg, window, not implicit)
|
||||
else:
|
||||
curtab = self._cntwidget(count)
|
||||
if curtab is None:
|
||||
if count is None:
|
||||
# We want to open a URL in the current tab, but none exists
|
||||
# yet.
|
||||
self._tabbed_browser.tabopen(url)
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
urls = self._parse_url_input(url)
|
||||
for i, cur_url in enumerate(urls):
|
||||
if not window and i > 0:
|
||||
tab = False
|
||||
bg = True
|
||||
if tab or bg or window:
|
||||
self._open(cur_url, tab, bg, window, not implicit)
|
||||
else:
|
||||
curtab.openurl(url)
|
||||
curtab = self._cntwidget(count)
|
||||
if curtab is None:
|
||||
if count is None:
|
||||
# We want to open a URL in the current tab, but none
|
||||
# exists yet.
|
||||
self._tabbed_browser.tabopen(cur_url)
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
else:
|
||||
curtab.openurl(cur_url)
|
||||
|
||||
def _parse_url(self, url, *, force_search=False):
|
||||
"""Parse a URL or quickmark or search query.
|
||||
|
||||
Args:
|
||||
url: The URL to parse.
|
||||
force_search: Whether to force a search even if the content can be
|
||||
interpreted as a URL or a path.
|
||||
|
||||
Return:
|
||||
A URL that can be opened.
|
||||
"""
|
||||
try:
|
||||
return objreg.get('quickmark-manager').get(url)
|
||||
except urlmarks.Error:
|
||||
try:
|
||||
return urlutils.fuzzy_url(url, force_search=force_search)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
# We don't use cmdexc.CommandError here as this can be
|
||||
# called async from edit_url
|
||||
message.error(self._win_id, str(e))
|
||||
return None
|
||||
|
||||
def _parse_url_input(self, url):
|
||||
"""Parse a URL or newline-separated list of URLs.
|
||||
|
||||
Args:
|
||||
url: The URL or list to parse.
|
||||
|
||||
Return:
|
||||
A list of URLs that can be opened.
|
||||
"""
|
||||
force_search = False
|
||||
urllist = [u for u in url.split('\n') if u.strip()]
|
||||
if (len(urllist) > 1 and not urlutils.is_url(urllist[0]) and
|
||||
urlutils.get_path_if_valid(urllist[0], check_exists=True)
|
||||
is None):
|
||||
urllist = [url]
|
||||
force_search = True
|
||||
for cur_url in urllist:
|
||||
parsed = self._parse_url(cur_url, force_search=force_search)
|
||||
if parsed is not None:
|
||||
yield parsed
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='reload',
|
||||
scope='window')
|
||||
@ -387,7 +427,7 @@ class CommandDispatcher:
|
||||
url = self._current_url()
|
||||
self._open(url, window=True)
|
||||
cur_widget = self._current_widget()
|
||||
self._tabbed_browser.close_tab(cur_widget)
|
||||
self._tabbed_browser.close_tab(cur_widget, add_undo=False)
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
@ -796,7 +836,8 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated="Use :open {clipboard}")
|
||||
def paste(self, sel=False, tab=False, bg=False, window=False):
|
||||
"""Open a page from the clipboard.
|
||||
|
||||
@ -810,15 +851,12 @@ class CommandDispatcher:
|
||||
window: Open in new window.
|
||||
"""
|
||||
force_search = False
|
||||
if sel and utils.supports_selection():
|
||||
target = "Primary selection"
|
||||
else:
|
||||
if not utils.supports_selection():
|
||||
sel = False
|
||||
target = "Clipboard"
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
if not text.strip():
|
||||
raise cmdexc.CommandError("{} is empty.".format(target))
|
||||
log.misc.debug("{} contained: {!r}".format(target, text))
|
||||
try:
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
text_urls = [u for u in text.split('\n') if u.strip()]
|
||||
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
|
||||
urlutils.get_path_if_valid(
|
||||
@ -1463,9 +1501,12 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("Focused element is not editable!")
|
||||
|
||||
try:
|
||||
sel = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
sel = utils.get_clipboard()
|
||||
try:
|
||||
sel = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
sel = utils.get_clipboard()
|
||||
except utils.ClipboardEmptyError:
|
||||
return
|
||||
|
||||
log.misc.debug("Pasting primary selection into element {}".format(
|
||||
elem.debug_text()))
|
||||
|
@ -21,24 +21,63 @@
|
||||
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message
|
||||
from qutebrowser.utils import message, log
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||
|
||||
This is needed because QtWebEngine likes to randomly change its
|
||||
focusProxy...
|
||||
|
||||
FIXME:qtwebengine Add a test for this happening
|
||||
|
||||
Attributes:
|
||||
_filter: The event filter to install.
|
||||
_widget: The widget expected to send out childEvents.
|
||||
"""
|
||||
|
||||
def __init__(self, eventfilter, widget, parent=None):
|
||||
super().__init__(parent)
|
||||
self._filter = eventfilter
|
||||
assert widget is not None
|
||||
self._widget = widget
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Act on ChildAdded events."""
|
||||
if event.type() == QEvent.ChildAdded:
|
||||
child = event.child()
|
||||
log.mouse.debug("{} got new child {}, installing filter".format(
|
||||
obj, child))
|
||||
assert obj is self._widget
|
||||
child.installEventFilter(self._filter)
|
||||
return False
|
||||
|
||||
|
||||
class MouseEventFilter(QObject):
|
||||
|
||||
"""Handle mouse events on a tab."""
|
||||
"""Handle mouse events on a tab.
|
||||
|
||||
Attributes:
|
||||
_tab: The browsertab object this filter is installed on.
|
||||
_handlers: A dict of handler functions for the handled events.
|
||||
_ignore_wheel_event: Whether to ignore the next wheelEvent.
|
||||
"""
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._handlers = {
|
||||
QEvent.MouseButtonPress: self._handle_mouse_press,
|
||||
QEvent.Wheel: self._handle_wheel,
|
||||
}
|
||||
self._ignore_wheel_event = False
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
def _handle_mouse_press(self, _obj, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
@ -46,6 +85,29 @@ class MouseEventFilter(QObject):
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
self._mousepress_backforward(e)
|
||||
return True
|
||||
self._ignore_wheel_event = True
|
||||
return False
|
||||
|
||||
def _handle_wheel(self, _obj, e):
|
||||
"""Zoom on Ctrl-Mousewheel.
|
||||
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
"""
|
||||
if self._ignore_wheel_event:
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/395
|
||||
self._ignore_wheel_event = False
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
if factor < 0:
|
||||
return False
|
||||
perc = int(100 * factor)
|
||||
message.info(self._tab.win_id, "Zoom level: {}%".format(perc))
|
||||
self._tab.zoom.set_factor(factor)
|
||||
|
||||
return False
|
||||
|
||||
def _mousepress_backforward(self, e):
|
||||
@ -69,9 +131,9 @@ class MouseEventFilter(QObject):
|
||||
message.error(self._tab.win_id, "At end of history.",
|
||||
immediately=True)
|
||||
|
||||
def eventFilter(self, _obj, event):
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events going to a QWeb(Engine)View."""
|
||||
evtype = event.type()
|
||||
if evtype not in self._handlers:
|
||||
return False
|
||||
return self._handlers[evtype](event)
|
||||
return self._handlers[evtype](obj, event)
|
||||
|
@ -31,7 +31,7 @@ from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
# pylint: enable=no-name-in-module,import-error,useless-suppression
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.browser import browsertab, mouse
|
||||
from qutebrowser.browser.webengine import webview, webengineelem
|
||||
from qutebrowser.utils import usertypes, qtutils, log, javascript, utils
|
||||
|
||||
@ -335,6 +335,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
self.backend = usertypes.Backend.QtWebEngine
|
||||
# init js stuff
|
||||
self._init_js()
|
||||
self._child_event_filter = None
|
||||
|
||||
def _init_js(self):
|
||||
js_code = '\n'.join([
|
||||
@ -359,8 +360,12 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
# FIXME:qtwebengine what about runsOnSubFrames?
|
||||
page.scripts().insert(script)
|
||||
|
||||
def _event_filter_target(self):
|
||||
return self._widget.focusProxy()
|
||||
def _install_event_filter(self):
|
||||
self._widget.focusProxy().installEventFilter(self._mouse_event_filter)
|
||||
self._child_event_filter = mouse.ChildEventFilter(
|
||||
eventfilter=self._mouse_event_filter, widget=self._widget,
|
||||
parent=self)
|
||||
self._widget.installEventFilter(self._child_event_filter)
|
||||
|
||||
def openurl(self, url):
|
||||
self._openurl_prepare(url)
|
||||
|
@ -33,24 +33,10 @@ class WebEngineView(QWebEngineView):
|
||||
|
||||
"""Custom QWebEngineView subclass with qutebrowser-specific features."""
|
||||
|
||||
mouse_wheel_zoom = pyqtSignal(QPoint)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setPage(WebEnginePage(self))
|
||||
|
||||
def wheelEvent(self, e):
|
||||
"""Zoom on Ctrl-Mousewheel.
|
||||
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
"""
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
e.accept()
|
||||
self.mouse_wheel_zoom.emit(e.angleDelta())
|
||||
else:
|
||||
super().wheelEvent(e)
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
|
||||
|
@ -510,8 +510,8 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
self.zoom.set_default()
|
||||
self.backend = usertypes.Backend.QtWebKit
|
||||
|
||||
def _event_filter_target(self):
|
||||
return self._widget
|
||||
def _install_event_filter(self):
|
||||
self._widget.installEventFilter(self._mouse_event_filter)
|
||||
|
||||
def openurl(self, url):
|
||||
self._openurl_prepare(url)
|
||||
|
@ -47,22 +47,16 @@ class WebView(QWebView):
|
||||
_old_scroll_pos: The old scroll position.
|
||||
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
||||
need to enter/leave insert mode.
|
||||
_ignore_wheel_event: Ignore the next wheel event.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/395
|
||||
|
||||
Signals:
|
||||
scroll_pos_changed: Scroll percentage of current tab changed.
|
||||
arg 1: x-position in %.
|
||||
arg 2: y-position in %.
|
||||
mouse_wheel_zoom: Emitted when the page should be zoomed because the
|
||||
mousewheel was used with ctrl.
|
||||
arg 1: The angle delta of the wheel event (QPoint)
|
||||
shutting_down: Emitted when the view is shutting down.
|
||||
"""
|
||||
|
||||
scroll_pos_changed = pyqtSignal(int, int)
|
||||
shutting_down = pyqtSignal()
|
||||
mouse_wheel_zoom = pyqtSignal(QPoint)
|
||||
|
||||
def __init__(self, win_id, tab_id, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -75,7 +69,6 @@ class WebView(QWebView):
|
||||
self._check_insertmode = False
|
||||
self.scroll_pos = (-1, -1)
|
||||
self._old_scroll_pos = (-1, -1)
|
||||
self._ignore_wheel_event = False
|
||||
self._set_bg_color()
|
||||
self._tab_id = tab_id
|
||||
|
||||
@ -388,7 +381,6 @@ class WebView(QWebView):
|
||||
"""
|
||||
self._mousepress_insertmode(e)
|
||||
self._mousepress_opentarget(e)
|
||||
self._ignore_wheel_event = True
|
||||
super().mousePressEvent(e)
|
||||
|
||||
def mouseReleaseEvent(self, e):
|
||||
@ -404,19 +396,3 @@ class WebView(QWebView):
|
||||
self.shutting_down.connect(menu.close)
|
||||
modeman.instance(self.win_id).entered.connect(menu.close)
|
||||
menu.exec_(e.globalPos())
|
||||
|
||||
def wheelEvent(self, e):
|
||||
"""Zoom on Ctrl-Mousewheel.
|
||||
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
"""
|
||||
if self._ignore_wheel_event:
|
||||
self._ignore_wheel_event = False
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/395
|
||||
return
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
e.accept()
|
||||
self.mouse_wheel_zoom.emit(e.angleDelta())
|
||||
else:
|
||||
super().wheelEvent(e)
|
||||
|
@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, objreg, qtutils
|
||||
from qutebrowser.utils import message, objreg, qtutils, utils
|
||||
from qutebrowser.misc import split
|
||||
|
||||
|
||||
@ -49,21 +49,29 @@ def _current_url(tabbed_browser):
|
||||
|
||||
def replace_variables(win_id, arglist):
|
||||
"""Utility function to replace variables like {url} in a list of args."""
|
||||
variables = {
|
||||
'{url}': lambda: _current_url(tabbed_browser).toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword),
|
||||
'{url:pretty}': lambda: _current_url(tabbed_browser).toString(
|
||||
QUrl.RemovePassword),
|
||||
'{clipboard}': utils.get_clipboard,
|
||||
'{primary}': lambda: utils.get_clipboard(selection=True),
|
||||
}
|
||||
values = {}
|
||||
args = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
if any('{url}' in arg for arg in arglist):
|
||||
url = _current_url(tabbed_browser).toString(QUrl.FullyEncoded |
|
||||
QUrl.RemovePassword)
|
||||
if any('{url:pretty}' in arg for arg in arglist):
|
||||
pretty_url = _current_url(tabbed_browser).toString(QUrl.RemovePassword)
|
||||
for arg in arglist:
|
||||
if '{url}' in arg:
|
||||
args.append(arg.replace('{url}', url))
|
||||
elif '{url:pretty}' in arg:
|
||||
args.append(arg.replace('{url:pretty}', pretty_url))
|
||||
else:
|
||||
|
||||
try:
|
||||
for arg in arglist:
|
||||
for var, func in variables.items():
|
||||
if var in arg:
|
||||
if var not in values:
|
||||
values[var] = func()
|
||||
arg = arg.replace(var, values[var])
|
||||
args.append(arg)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
return args
|
||||
|
||||
|
||||
|
@ -233,7 +233,9 @@ def data(readonly=False):
|
||||
('last-opened', "Open new tabs in the last opened "
|
||||
"window."),
|
||||
('last-focused', "Open new tabs in the most recently "
|
||||
"focused window.")
|
||||
"focused window."),
|
||||
('last-visible', "Open new tabs in the most recently "
|
||||
"visible window.")
|
||||
)), 'last-focused'),
|
||||
"Which window to choose when opening links as new tabs."),
|
||||
|
||||
@ -1521,12 +1523,12 @@ KEY_DATA = collections.OrderedDict([
|
||||
('yank domain -s', ['yD']),
|
||||
('yank pretty-url', ['yp']),
|
||||
('yank pretty-url -s', ['yP']),
|
||||
('paste', ['pp']),
|
||||
('paste -s', ['pP']),
|
||||
('paste -t', ['Pp']),
|
||||
('paste -ts', ['PP']),
|
||||
('paste -w', ['wp']),
|
||||
('paste -ws', ['wP']),
|
||||
('open {clipboard}', ['pp']),
|
||||
('open {primary}', ['pP']),
|
||||
('open -t {clipboard}', ['Pp']),
|
||||
('open -t {primary}', ['PP']),
|
||||
('open -w {clipboard}', ['wp']),
|
||||
('open -w {primary}', ['wP']),
|
||||
('quickmark-save', ['m']),
|
||||
('set-cmd-text -s :quickmark-load', ['b']),
|
||||
('set-cmd-text -s :quickmark-load -t', ['B']),
|
||||
@ -1696,6 +1698,11 @@ CHANGED_KEY_COMMANDS = [
|
||||
(re.compile(r'^yank-selected -p'), r'yank selection -s'),
|
||||
(re.compile(r'^yank-selected'), r'yank selection'),
|
||||
|
||||
(re.compile(r'^paste$'), r'open {clipboard}'),
|
||||
(re.compile(r'^paste -([twb])$'), r'open -\1 {clipboard}'),
|
||||
(re.compile(r'^paste -([twb])s$'), r'open -\1 {primary}'),
|
||||
(re.compile(r'^paste -s([twb])$'), r'open -\1 {primary}'),
|
||||
|
||||
(re.compile(r'^completion-item-next'), r'completion-item-focus next'),
|
||||
(re.compile(r'^completion-item-prev'), r'completion-item-focus prev'),
|
||||
]
|
||||
|
@ -28,26 +28,41 @@ window._qutebrowser.scroll = (function() {
|
||||
var y_px = window.scrollY;
|
||||
|
||||
if (x !== undefined) {
|
||||
x_px = (elem.scrollWidth - elem.clientWidth) / 100 * x;
|
||||
x_px = (elem.scrollWidth - window.innerWidth) / 100 * x;
|
||||
}
|
||||
|
||||
if (y !== undefined) {
|
||||
y_px = (elem.scrollHeight - elem.clientHeight) / 100 * y;
|
||||
y_px = (elem.scrollHeight - window.innerHeight) / 100 * y;
|
||||
}
|
||||
|
||||
/*
|
||||
console.log(JSON.stringify({
|
||||
"x": x,
|
||||
"window.scrollX": window.scrollX,
|
||||
"window.innerWidth": window.innerWidth,
|
||||
"elem.scrollWidth": elem.scrollWidth,
|
||||
"x_px": x_px,
|
||||
"y": y,
|
||||
"window.scrollY": window.scrollY,
|
||||
"window.innerHeight": window.innerHeight,
|
||||
"elem.scrollHeight": elem.scrollHeight,
|
||||
"y_px": y_px,
|
||||
}));
|
||||
*/
|
||||
|
||||
window.scroll(x_px, y_px);
|
||||
};
|
||||
|
||||
funcs.delta_page = function(x, y) {
|
||||
var dx = document.documentElement.clientWidth * x;
|
||||
var dy = document.documentElement.clientHeight * y;
|
||||
var dx = window.innerWidth * x;
|
||||
var dy = window.innerHeight * y;
|
||||
window.scrollBy(dx, dy);
|
||||
};
|
||||
|
||||
funcs.pos = function() {
|
||||
var elem = document.documentElement;
|
||||
var dx = elem.scrollWidth - elem.clientWidth;
|
||||
var dy = elem.scrollHeight - elem.clientHeight;
|
||||
var dx = elem.scrollWidth - window.innerWidth;
|
||||
var dy = elem.scrollHeight - window.innerHeight;
|
||||
var perc_x, perc_y;
|
||||
|
||||
if (dx === 0) {
|
||||
|
@ -74,6 +74,8 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
window = objreg.last_focused_window()
|
||||
elif win_mode == 'last-opened':
|
||||
window = objreg.last_window()
|
||||
elif win_mode == 'last-visible':
|
||||
window = objreg.last_visible_window()
|
||||
except objreg.NoWindow:
|
||||
# There is no window left, so we open a new one
|
||||
window = MainWindow()
|
||||
@ -458,8 +460,23 @@ class MainWindow(QWidget):
|
||||
self._downloadview.updateGeometry()
|
||||
self.tabbed_browser.tabBar().refresh()
|
||||
|
||||
def showEvent(self, e):
|
||||
"""Extend showEvent to register us as the last-visible-main-window.
|
||||
|
||||
Args:
|
||||
e: The QShowEvent
|
||||
"""
|
||||
super().showEvent(e)
|
||||
objreg.register('last-visible-main-window', self, update=True)
|
||||
|
||||
def _do_close(self):
|
||||
"""Helper function for closeEvent."""
|
||||
last_visible = objreg.get('last-visible-main-window')
|
||||
if self is last_visible:
|
||||
try:
|
||||
objreg.delete('last-visible-main-window')
|
||||
except KeyError:
|
||||
pass
|
||||
objreg.get('session-manager').save_last_window_session()
|
||||
self._save_geometry()
|
||||
log.destroy.debug("Closing window {}".format(self.win_id))
|
||||
|
@ -217,11 +217,12 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
for tab in self.widgets():
|
||||
self._remove_tab(tab)
|
||||
|
||||
def close_tab(self, tab):
|
||||
def close_tab(self, tab, *, add_undo=True):
|
||||
"""Close a tab.
|
||||
|
||||
Args:
|
||||
tab: The QWebView to be closed.
|
||||
add_undo: Whether the tab close can be undone.
|
||||
"""
|
||||
last_close = config.get('tabs', 'last-close')
|
||||
count = self.count()
|
||||
@ -229,7 +230,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
if last_close == 'ignore' and count == 1:
|
||||
return
|
||||
|
||||
self._remove_tab(tab)
|
||||
self._remove_tab(tab, add_undo=add_undo)
|
||||
|
||||
if count == 1: # We just closed the last tab above.
|
||||
if last_close == 'close':
|
||||
@ -243,11 +244,12 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
url = config.get('general', 'default-page')
|
||||
self.openurl(url, newtab=True)
|
||||
|
||||
def _remove_tab(self, tab):
|
||||
def _remove_tab(self, tab, *, add_undo=True):
|
||||
"""Remove a tab from the tab list and delete it properly.
|
||||
|
||||
Args:
|
||||
tab: The QWebView to be closed.
|
||||
add_undo: Whether the tab close can be undone.
|
||||
"""
|
||||
idx = self.indexOf(tab)
|
||||
if idx == -1:
|
||||
@ -261,8 +263,9 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
window=self._win_id)
|
||||
if tab.url().isValid():
|
||||
history_data = tab.history.serialize()
|
||||
entry = UndoEntry(tab.url(), history_data, idx)
|
||||
self._undo_stack.append(entry)
|
||||
if add_undo:
|
||||
entry = UndoEntry(tab.url(), history_data, idx)
|
||||
self._undo_stack.append(entry)
|
||||
elif tab.url().isEmpty():
|
||||
# There are some good reasons why a URL could be empty
|
||||
# (target="_blank" with a download, see [1]), so we silently ignore
|
||||
|
@ -47,7 +47,7 @@ class MinimalLineEditMixin:
|
||||
if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier:
|
||||
try:
|
||||
text = utils.get_clipboard(selection=True)
|
||||
except utils.SelectionUnsupportedError:
|
||||
except utils.ClipboardError:
|
||||
pass
|
||||
else:
|
||||
e.accept()
|
||||
|
@ -285,6 +285,14 @@ def dump_objects():
|
||||
return lines
|
||||
|
||||
|
||||
def last_visible_window():
|
||||
"""Get the last visible window, or the last focused window if none."""
|
||||
try:
|
||||
return get('last-visible-main-window')
|
||||
except KeyError:
|
||||
return last_focused_window()
|
||||
|
||||
|
||||
def last_focused_window():
|
||||
"""Get the last focused window, or the last window if none."""
|
||||
try:
|
||||
|
@ -43,11 +43,21 @@ fake_clipboard = None
|
||||
log_clipboard = False
|
||||
|
||||
|
||||
class SelectionUnsupportedError(Exception):
|
||||
class ClipboardError(Exception):
|
||||
|
||||
"""Raised if the clipboard contents are unavailable for some reason."""
|
||||
|
||||
|
||||
class SelectionUnsupportedError(ClipboardError):
|
||||
|
||||
"""Raised if [gs]et_clipboard is used and selection=True is unsupported."""
|
||||
|
||||
|
||||
class ClipboardEmptyError(ClipboardError):
|
||||
|
||||
"""Raised if get_clipboard is used and the clipboard is empty."""
|
||||
|
||||
|
||||
def elide(text, length):
|
||||
"""Elide text so it uses a maximum of length chars."""
|
||||
if length < 1:
|
||||
@ -810,6 +820,11 @@ def get_clipboard(selection=False):
|
||||
mode = QClipboard.Selection if selection else QClipboard.Clipboard
|
||||
data = QApplication.clipboard().text(mode=mode)
|
||||
|
||||
target = "Primary selection" if selection else "Clipboard"
|
||||
if not data.strip():
|
||||
raise ClipboardEmptyError("{} is empty.".format(target))
|
||||
log.misc.debug("{} contained: {!r}".format(target, data))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
@ -327,11 +327,13 @@ def generate_commands(filename):
|
||||
for name, cmd in cmdutils.cmd_dict.items():
|
||||
if name in cmdutils.aliases:
|
||||
continue
|
||||
if cmd.deprecated:
|
||||
continue
|
||||
if cmd.hide:
|
||||
hidden_cmds.append((name, cmd))
|
||||
elif cmd.debug:
|
||||
debug_cmds.append((name, cmd))
|
||||
elif not cmd.deprecated:
|
||||
else:
|
||||
normal_cmds.append((name, cmd))
|
||||
normal_cmds.sort()
|
||||
hidden_cmds.sort()
|
||||
|
@ -6,6 +6,6 @@
|
||||
<title>Scrolling inside an iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="margin: 50px;" src="/data/scroll.html"></iframe>
|
||||
<iframe style="margin: 50px;" src="/data/scroll/simple.html"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
|
214
tests/end2end/data/scroll/no_doctype.html
Normal file
214
tests/end2end/data/scroll/no_doctype.html
Normal file
@ -0,0 +1,214 @@
|
||||
<!-- This is the same as scroll.html but without <!DOCTYPE html> -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Scrolling without doctype</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
30
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
50
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
60
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
69
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
74
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
80
|
||||
81
|
||||
82
|
||||
83
|
||||
84
|
||||
85
|
||||
86
|
||||
87
|
||||
88
|
||||
89
|
||||
90
|
||||
91
|
||||
92
|
||||
93
|
||||
94
|
||||
95
|
||||
96
|
||||
97
|
||||
98
|
||||
99
|
||||
100
|
||||
101
|
||||
102
|
||||
103
|
||||
104
|
||||
105
|
||||
106
|
||||
107
|
||||
108
|
||||
109
|
||||
110
|
||||
111
|
||||
112
|
||||
113
|
||||
114
|
||||
115
|
||||
116
|
||||
117
|
||||
118
|
||||
119
|
||||
120
|
||||
121
|
||||
122
|
||||
123
|
||||
124
|
||||
125
|
||||
126
|
||||
127
|
||||
128
|
||||
129
|
||||
130
|
||||
131
|
||||
132
|
||||
133
|
||||
134
|
||||
135
|
||||
136
|
||||
137
|
||||
138
|
||||
139
|
||||
140
|
||||
141
|
||||
142
|
||||
143
|
||||
144
|
||||
145
|
||||
146
|
||||
147
|
||||
148
|
||||
149
|
||||
150
|
||||
151
|
||||
152
|
||||
153
|
||||
154
|
||||
155
|
||||
156
|
||||
157
|
||||
158
|
||||
159
|
||||
160
|
||||
161
|
||||
162
|
||||
163
|
||||
164
|
||||
165
|
||||
166
|
||||
167
|
||||
168
|
||||
169
|
||||
170
|
||||
171
|
||||
172
|
||||
173
|
||||
174
|
||||
175
|
||||
176
|
||||
177
|
||||
178
|
||||
179
|
||||
180
|
||||
181
|
||||
182
|
||||
183
|
||||
184
|
||||
185
|
||||
186
|
||||
187
|
||||
188
|
||||
189
|
||||
190
|
||||
191
|
||||
192
|
||||
193
|
||||
194
|
||||
195
|
||||
196
|
||||
197
|
||||
198
|
||||
199
|
||||
This is a very long line so this page can be scrolled horizontally. Did you think this line would end here already? Nah, it does not. But now it will. Or will it? I think it's not long enough yet. But depending on your screen size, this is not even enough yet, so I'm typing some more gibberish here. Hopefully that helps. I'm glad if it did, I'm always happy to help. Really, you're welcome. Okay, okay, can I stop now?
|
||||
</pre>
|
||||
<a href="/data/hello2.txt">next</a> link to test the --top-navigate argument for :scroll-page.
|
||||
<a href="/data/hello3.txt">prev</a> link to test the --bottom-navigate argument for :scroll-page.
|
||||
</body>
|
||||
</html>
|
@ -497,7 +497,7 @@ def should_quit(qtbot, quteproc):
|
||||
|
||||
def _get_scroll_values(quteproc):
|
||||
data = quteproc.get_session()
|
||||
pos = data['windows'][0]['tabs'][0]['history'][0]['scroll-pos']
|
||||
pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos']
|
||||
return (pos['x'], pos['y'])
|
||||
|
||||
|
||||
|
@ -488,7 +488,7 @@ Feature: Various utility commands.
|
||||
Then the page should contain the plaintext "Local storage status: not working"
|
||||
|
||||
Scenario: :repeat-command
|
||||
Given I open data/scroll.html
|
||||
Given I open data/scroll/simple.html
|
||||
And I run :tab-only
|
||||
When I run :scroll down
|
||||
And I run :repeat-command
|
||||
@ -496,7 +496,7 @@ Feature: Various utility commands.
|
||||
Then the page should be scrolled vertically
|
||||
|
||||
Scenario: :repeat-command with count
|
||||
Given I open data/scroll.html
|
||||
Given I open data/scroll/simple.html
|
||||
And I run :tab-only
|
||||
When I run :scroll down with count 3
|
||||
And I run :scroll up
|
||||
@ -504,7 +504,7 @@ Feature: Various utility commands.
|
||||
Then the page should not be scrolled
|
||||
|
||||
Scenario: :repeat-command with not-normal command inbetween
|
||||
Given I open data/scroll.html
|
||||
Given I open data/scroll/simple.html
|
||||
And I run :tab-only
|
||||
When I run :scroll down with count 3
|
||||
And I run :scroll up
|
||||
@ -524,3 +524,16 @@ Feature: Various utility commands.
|
||||
Then the following tabs should be open:
|
||||
- data/hints/link_blank.html
|
||||
- data/hello.txt (active)
|
||||
|
||||
## Variables
|
||||
|
||||
Scenario: {url} as part of an argument
|
||||
When I open data/hello.txt
|
||||
And I run :message-info foo{url}
|
||||
Then the message "foohttp://localhost:*/hello.txt" should be shown
|
||||
|
||||
Scenario: Multiple variables in an argument
|
||||
When I open data/hello.txt
|
||||
And I put "foo" into the clipboard
|
||||
And I run :message-info {clipboard}bar{url}
|
||||
Then the message "foobarhttp://localhost:*/hello.txt" should be shown
|
||||
|
@ -2,7 +2,7 @@ Feature: Scrolling
|
||||
Tests the various scroll commands.
|
||||
|
||||
Background:
|
||||
Given I open data/scroll.html
|
||||
Given I open data/scroll/simple.html
|
||||
And I run :tab-only
|
||||
|
||||
## :scroll-px
|
||||
@ -193,6 +193,12 @@ Feature: Scrolling
|
||||
Scenario: :scroll-perc with count and argument
|
||||
When I run :scroll-perc 0 with count 50
|
||||
Then the page should be scrolled vertically
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/1821
|
||||
Scenario: :scroll-perc without doctype
|
||||
When I open data/scroll/no_doctype.html
|
||||
And I run :scroll-perc 100
|
||||
Then the page should be scrolled vertically
|
||||
|
||||
## :scroll-page
|
||||
|
||||
|
@ -863,13 +863,13 @@ Feature: Tab management
|
||||
Scenario: :buffer with a matching title
|
||||
When I open data/title.html
|
||||
And I open data/search.html in a new tab
|
||||
And I open data/scroll.html in a new tab
|
||||
And I open data/scroll/simple.html in a new tab
|
||||
And I run :buffer "Searching text"
|
||||
And I wait for "Current tab changed, focusing <qutebrowser.browser.* tab_id=* url='http://localhost:*/data/search.html'>" in the log
|
||||
Then the following tabs should be open:
|
||||
- data/title.html
|
||||
- data/search.html (active)
|
||||
- data/scroll.html
|
||||
- data/scroll/simple.html
|
||||
|
||||
Scenario: :buffer with no matching title
|
||||
When I run :buffer "invalid title"
|
||||
@ -878,11 +878,11 @@ Feature: Tab management
|
||||
Scenario: :buffer with matching title and two windows
|
||||
When I open data/title.html
|
||||
And I open data/search.html in a new tab
|
||||
And I open data/scroll.html in a new tab
|
||||
And I open data/scroll/simple.html in a new tab
|
||||
And I open data/caret.html in a new window
|
||||
And I open data/paste_primary.html in a new tab
|
||||
And I run :buffer "Scrolling"
|
||||
And I wait for "Focus object changed: <qutebrowser.browser.* tab_id=* url='http://localhost:*/data/scroll.html'>" in the log
|
||||
And I wait for "Focus object changed: <qutebrowser.browser.* tab_id=* url='http://localhost:*/data/scroll/simple.html'>" in the log
|
||||
Then the session should look like:
|
||||
windows:
|
||||
- active: true
|
||||
@ -894,7 +894,7 @@ Feature: Tab management
|
||||
- url: http://localhost:*/data/search.html
|
||||
- active: true
|
||||
history:
|
||||
- url: http://localhost:*/data/scroll.html
|
||||
- url: http://localhost:*/data/scroll/simple.html
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/caret.html
|
||||
@ -916,7 +916,7 @@ Feature: Tab management
|
||||
Given I have a fresh instance
|
||||
When I open data/title.html
|
||||
And I open data/search.html in a new tab
|
||||
And I open data/scroll.html in a new tab
|
||||
And I open data/scroll/simple.html in a new tab
|
||||
And I run :open -w http://localhost:(port)/data/caret.html
|
||||
And I open data/paste_primary.html in a new tab
|
||||
And I wait until data/caret.html is loaded
|
||||
@ -933,7 +933,7 @@ Feature: Tab management
|
||||
history:
|
||||
- url: http://localhost:*/data/search.html
|
||||
- history:
|
||||
- url: http://localhost:*/data/scroll.html
|
||||
- url: http://localhost:*/data/scroll/simple.html
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/caret.html
|
||||
|
@ -1,6 +1,6 @@
|
||||
Feature: Yanking and pasting.
|
||||
:yank and :paste can be used to copy/paste the URL or title from/to the
|
||||
clipboard and primary selection.
|
||||
:yank, {clipboard} and {primary} can be used to copy/paste the URL or title
|
||||
from/to the clipboard and primary selection.
|
||||
|
||||
Background:
|
||||
Given I run :tab-only
|
||||
@ -45,11 +45,11 @@ Feature: Yanking and pasting.
|
||||
Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title with spaces.html" should be shown
|
||||
And the clipboard should contain "http://localhost:(port)/data/title with spaces.html"
|
||||
|
||||
#### :paste
|
||||
#### {clipboard} and {primary}
|
||||
|
||||
Scenario: Pasting a URL
|
||||
When I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the requests should be:
|
||||
data/hello.txt
|
||||
@ -57,32 +57,32 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting a URL from primary selection
|
||||
When selection is supported
|
||||
And I put "http://localhost:(port)/data/hello2.txt" into the primary selection
|
||||
And I run :paste --sel
|
||||
And I run :open {primary}
|
||||
And I wait until data/hello2.txt is loaded
|
||||
Then the requests should be:
|
||||
data/hello2.txt
|
||||
|
||||
Scenario: Pasting with empty clipboard
|
||||
When I put "" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard} (invalid command)
|
||||
Then the error "Clipboard is empty." should be shown
|
||||
|
||||
Scenario: Pasting with empty selection
|
||||
When selection is supported
|
||||
And I put "" into the primary selection
|
||||
And I run :paste --sel
|
||||
And I run :open {primary} (invalid command)
|
||||
Then the error "Primary selection is empty." should be shown
|
||||
|
||||
Scenario: Pasting with a space in clipboard
|
||||
When I put " " into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard} (invalid command)
|
||||
Then the error "Clipboard is empty." should be shown
|
||||
|
||||
Scenario: Pasting in a new tab
|
||||
Given I open about:blank
|
||||
When I run :tab-only
|
||||
And I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -92,7 +92,7 @@ Feature: Yanking and pasting.
|
||||
Given I open about:blank
|
||||
When I run :tab-only
|
||||
And I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -b
|
||||
And I run :open -b {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank (active)
|
||||
@ -101,7 +101,7 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting in a new window
|
||||
Given I have a fresh instance
|
||||
When I put "http://localhost:(port)/data/hello.txt" into the clipboard
|
||||
And I run :paste -w
|
||||
And I run :open -w {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the session should look like:
|
||||
windows:
|
||||
@ -119,7 +119,7 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting an invalid URL
|
||||
When I set general -> auto-search to false
|
||||
And I put "foo bar" into the clipboard
|
||||
And I run :paste
|
||||
And I run :open {clipboard}
|
||||
Then the error "Invalid URL" should be shown
|
||||
|
||||
Scenario: Pasting multiple urls in a new tab
|
||||
@ -128,7 +128,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -145,7 +145,7 @@ Feature: Yanking and pasting.
|
||||
this url:
|
||||
http://qutebrowser.org
|
||||
should not open
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt?q=this%20url%3A%0Ahttp%3A//qutebrowser.org%0Ashould%20not%20open is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -159,7 +159,7 @@ Feature: Yanking and pasting.
|
||||
text:
|
||||
should open
|
||||
as search
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
And I wait until data/hello.txt?q=text%3A%0Ashould%20open%0Aas%20search is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
@ -172,7 +172,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -b
|
||||
And I run :open -b {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -188,7 +188,7 @@ Feature: Yanking and pasting.
|
||||
http://localhost:(port)/data/hello.txt
|
||||
http://localhost:(port)/data/hello2.txt
|
||||
http://localhost:(port)/data/hello3.txt
|
||||
And I run :paste -w
|
||||
And I run :open -w {clipboard}
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I wait until data/hello2.txt is loaded
|
||||
And I wait until data/hello3.txt is loaded
|
||||
@ -218,13 +218,13 @@ Feature: Yanking and pasting.
|
||||
Scenario: Pasting multiple urls with an empty one
|
||||
When I open about:blank
|
||||
And I put "http://localhost:(port)/data/hello.txt\n\nhttp://localhost:(port)/data/hello2.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
Then no crash should happen
|
||||
|
||||
Scenario: Pasting multiple urls with an almost empty one
|
||||
When I open about:blank
|
||||
And I put "http://localhost:(port)/data/hello.txt\n \nhttp://localhost:(port)/data/hello2.txt" into the clipboard
|
||||
And I run :paste -t
|
||||
And I run :open -t {clipboard}
|
||||
Then no crash should happen
|
||||
|
||||
#### :paste-primary
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import PYQT_VERSION, pyqtSignal, QPoint
|
||||
from PyQt5.QtCore import PYQT_VERSION, pyqtSignal
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.keyinput import modeman
|
||||
@ -29,22 +29,16 @@ pytestmark = pytest.mark.usefixtures('redirect_xdg_data')
|
||||
|
||||
try:
|
||||
from PyQt5.QtWebKitWidgets import QWebView
|
||||
|
||||
class WebView(QWebView):
|
||||
mouse_wheel_zoom = pyqtSignal(QPoint)
|
||||
except ImportError:
|
||||
WebView = None
|
||||
QWebView = None
|
||||
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
mouse_wheel_zoom = pyqtSignal(QPoint)
|
||||
except ImportError:
|
||||
WebEngineView = None
|
||||
QWebEngineView = None
|
||||
|
||||
|
||||
@pytest.fixture(params=[WebView, WebEngineView])
|
||||
@pytest.fixture(params=[QWebView, QWebEngineView])
|
||||
def view(qtbot, config_stub, request):
|
||||
config_stub.data = {
|
||||
'input': {
|
||||
@ -107,8 +101,8 @@ class Tab(browsertab.AbstractTab):
|
||||
self.search = browsertab.AbstractSearch(parent=self)
|
||||
self.printing = browsertab.AbstractPrinting()
|
||||
|
||||
def _event_filter_target(self):
|
||||
return self._widget
|
||||
def _install_event_filter(self):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skipif(PYQT_VERSION < 0x050600,
|
||||
|
@ -298,6 +298,10 @@ class TestKeyConfigParser:
|
||||
('yank -ds', 'yank domain -s'),
|
||||
('yank -p', 'yank pretty-url'),
|
||||
('yank -ps', 'yank pretty-url -s'),
|
||||
|
||||
('paste', 'open {clipboard}'),
|
||||
('paste -t', 'open -t {clipboard}'),
|
||||
('paste -ws', 'open -w {primary}'),
|
||||
]
|
||||
)
|
||||
def test_migrations(self, old, new_expected):
|
||||
|
Loading…
Reference in New Issue
Block a user