qutebrowser/qutebrowser/browser/commands.py

1984 lines
77 KiB
Python
Raw Normal View History

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2014-06-19 09:04:37 +02:00
2016-01-04 07:12:39 +01:00
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2014-04-17 09:44:26 +02:00
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
2014-05-27 15:56:44 +02:00
"""Command dispatcher for TabbedBrowser."""
2014-04-17 09:44:26 +02:00
2014-04-29 18:00:22 +02:00
import os
2015-11-24 18:04:10 +01:00
import os.path
2015-12-01 11:19:13 +01:00
import sys
import shlex
2014-09-22 21:13:42 +02:00
import posixpath
2014-09-28 22:13:14 +02:00
import functools
2015-05-31 17:56:27 +02:00
import xml.etree.ElementTree
2014-04-29 18:00:22 +02:00
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWidgets import QApplication, QTabBar
from PyQt5.QtCore import Qt, QUrl, QEvent
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
from PyQt5.QtWebKitWidgets import QWebPage
2014-09-15 17:59:54 +02:00
import pygments
import pygments.lexers
import pygments.formatters
2014-04-17 09:44:26 +02:00
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.config import config, configexc
from qutebrowser.browser import urlmarks
from qutebrowser.browser.webkit import webelem, inspector, downloads, mhtml
2015-05-13 07:29:17 +02:00
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, typing)
2015-04-28 16:50:42 +02:00
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import instances, sortfilter
2014-04-17 09:44:26 +02:00
class CommandDispatcher:
2014-04-17 09:44:26 +02:00
"""Command dispatcher for TabbedBrowser.
Contains all commands which are related to the current tab.
We can't simply add these commands to BrowserTab directly and use
2014-05-17 22:38:07 +02:00
currentWidget() for TabbedBrowser.cmd because at the time
2014-04-17 09:44:26 +02:00
cmdutils.register() decorators are run, currentWidget() will return None.
Attributes:
_editor: The ExternalEditor object.
2014-09-28 22:13:14 +02:00
_win_id: The window ID the CommandDispatcher is associated with.
_tabbed_browser: The TabbedBrowser used.
2014-04-17 09:44:26 +02:00
"""
def __init__(self, win_id, tabbed_browser):
2014-09-28 22:13:14 +02:00
self._win_id = win_id
self._tabbed_browser = tabbed_browser
2014-04-17 09:44:26 +02:00
2014-09-23 23:31:17 +02:00
def __repr__(self):
2014-09-26 15:48:24 +02:00
return utils.get_repr(self)
2014-09-23 23:31:17 +02:00
def _new_tabbed_browser(self):
"""Get a tabbed-browser from a new window."""
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow()
new_window.show()
return new_window.tabbed_browser
2014-09-28 22:13:14 +02:00
def _count(self):
"""Convenience method to get the widget count."""
return self._tabbed_browser.count()
def _set_current_index(self, idx):
"""Convenience method to set the current widget index."""
return self._tabbed_browser.setCurrentIndex(idx)
def _current_index(self):
"""Convenience method to get the current widget index."""
return self._tabbed_browser.currentIndex()
def _current_url(self):
"""Convenience method to get the current url."""
try:
return self._tabbed_browser.current_url()
except qtutils.QtValueError as e:
msg = "Current URL is invalid"
if e.reason:
msg += " ({})".format(e.reason)
msg += "!"
raise cmdexc.CommandError(msg)
2015-05-22 00:17:22 +02:00
def _current_title(self):
"""Convenience method to get the current title."""
2015-07-12 02:28:31 +02:00
return self._current_widget().title()
2015-05-22 00:17:22 +02:00
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = self._tabbed_browser.currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
return widget
2015-03-30 12:14:15 +02:00
def _open(self, url, tab=False, background=False, window=False):
"""Helper function to open a page.
Args:
url: The URL to open as QUrl.
tab: Whether to open in a new tab.
background: Whether to open in the background.
window: Whether to open in a new window
"""
urlutils.raise_cmdexc_if_invalid(url)
tabbed_browser = self._tabbed_browser
cmdutils.check_exclusive((tab, background, window), 'tbw')
if window:
tabbed_browser = self._new_tabbed_browser()
tabbed_browser.tabopen(url)
elif tab:
tabbed_browser.tabopen(url, background=False, explicit=True)
elif background:
tabbed_browser.tabopen(url, background=True, explicit=True)
else:
widget = self._current_widget()
widget.openurl(url)
2014-09-24 19:53:31 +02:00
def _cntwidget(self, count=None):
"""Return a widget based on a count/idx.
Args:
count: The tab index, or None.
Return:
The current widget if count is None.
The widget with the given tab ID if count is given.
None if no widget was found.
"""
if count is None:
return self._tabbed_browser.currentWidget()
elif 1 <= count <= self._count():
2014-09-24 19:53:31 +02:00
cmdutils.check_overflow(count + 1, 'int')
return self._tabbed_browser.widget(count - 1)
2014-09-24 19:53:31 +02:00
else:
return None
2014-08-03 00:39:39 +02:00
def _tab_focus_last(self):
"""Select the tab which was last focused."""
try:
tab = objreg.get('last-focused-tab', scope='window',
window=self._win_id)
except KeyError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No last focused tab!")
idx = self._tabbed_browser.indexOf(tab)
2014-08-03 00:39:39 +02:00
if idx == -1:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Last focused tab vanished!")
self._set_current_index(idx)
2014-08-03 00:39:39 +02:00
def _get_selection_override(self, left, right, opposite):
"""Helper function for tab_close to get the tab to select.
Args:
left: Force selecting the tab to the left of the current tab.
right: Force selecting the tab to the right of the current tab.
2015-03-31 20:49:29 +02:00
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
Return:
QTabBar.SelectLeftTab, QTabBar.SelectRightTab, or None if no change
should be made.
"""
cmdutils.check_exclusive((left, right, opposite), 'lro')
if left:
return QTabBar.SelectLeftTab
elif right:
return QTabBar.SelectRightTab
elif opposite:
conf_selection = config.get('tabs', 'select-on-remove')
if conf_selection == QTabBar.SelectLeftTab:
return QTabBar.SelectRightTab
elif conf_selection == QTabBar.SelectRightTab:
return QTabBar.SelectLeftTab
elif conf_selection == QTabBar.SelectPreviousTab:
raise cmdexc.CommandError(
"-o is not supported with 'tabs->select-on-remove' set to "
"'previous'!")
2015-11-25 18:47:36 +01:00
else: # pragma: no cover
raise ValueError("Invalid select-on-remove value "
"{!r}!".format(conf_selection))
return None
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_close(self, left=False, right=False, opposite=False, count=None):
2014-05-17 22:38:07 +02:00
"""Close the current/[count]th tab.
Args:
left: Force selecting the tab to the left of the current tab.
right: Force selecting the tab to the right of the current tab.
2015-03-31 20:49:29 +02:00
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
2014-05-17 22:38:07 +02:00
count: The tab index to close, or None
"""
2014-09-24 19:53:31 +02:00
tab = self._cntwidget(count)
2014-05-17 22:38:07 +02:00
if tab is None:
return
tabbar = self._tabbed_browser.tabBar()
selection_override = self._get_selection_override(left, right,
opposite)
if selection_override is None:
self._tabbed_browser.close_tab(tab)
else:
old_selection_behavior = tabbar.selectionBehaviorOnRemove()
tabbar.setSelectionBehaviorOnRemove(selection_override)
self._tabbed_browser.close_tab(tab)
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='command-dispatcher', name='open',
2016-05-10 23:04:52 +02:00
maxsplit=0, scope='window')
@cmdutils.argument('url', completion=usertypes.Completion.url)
@cmdutils.argument('count', count=True)
def openurl(self, url=None, bg=False, tab=False, window=False, count=None):
"""Open a URL in the current/[count]th tab.
2014-04-17 09:44:26 +02:00
Args:
2014-09-13 00:22:27 +02:00
url: The URL to open.
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
2014-04-17 09:44:26 +02:00
count: The tab index to open the URL in, or None.
"""
if url is None:
if tab or bg or window:
url = config.get('general', 'default-page')
else:
raise cmdexc.CommandError("No URL given, but -t/-b/-w is not "
"set!")
else:
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)
2014-04-17 09:44:26 +02:00
else:
2014-09-24 19:53:31 +02:00
curtab = self._cntwidget(count)
2014-09-02 21:54:07 +02:00
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)
2014-09-02 21:54:07 +02:00
else:
# Explicit count with a tab that doesn't exist.
return
else:
curtab.openurl(url)
2014-04-17 09:44:26 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', name='reload',
scope='window')
@cmdutils.argument('count', count=True)
def reloadpage(self, force=False, count=None):
2014-04-17 09:44:26 +02:00
"""Reload the current/[count]th tab.
Args:
count: The tab index to reload, or None.
2014-12-19 15:27:22 +01:00
force: Bypass the page cache.
2014-04-17 09:44:26 +02:00
"""
2014-09-24 19:53:31 +02:00
tab = self._cntwidget(count)
2014-04-17 09:44:26 +02:00
if tab is not None:
2014-12-19 15:27:22 +01:00
if force:
2014-12-29 22:12:09 +01:00
tab.page().triggerAction(QWebPage.ReloadAndBypassCache)
2014-12-19 15:27:22 +01:00
else:
tab.reload()
2014-04-17 09:44:26 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def stop(self, count=None):
2014-04-17 09:44:26 +02:00
"""Stop loading in the current/[count]th tab.
Args:
count: The tab index to stop, or None.
"""
2014-09-24 19:53:31 +02:00
tab = self._cntwidget(count)
2014-04-17 09:44:26 +02:00
if tab is not None:
tab.stop()
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', name='print',
scope='window')
@cmdutils.argument('count', count=True)
def printpage(self, preview=False, count=None):
2014-04-17 09:44:26 +02:00
"""Print the current/[count]th tab.
Args:
2014-09-13 00:22:27 +02:00
preview: Show preview instead of printing.
2014-04-17 09:44:26 +02:00
count: The tab index to print, or None.
"""
2014-08-26 19:10:14 +02:00
if not qtutils.check_print_compat():
2014-09-02 20:44:58 +02:00
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
2014-09-24 19:53:31 +02:00
tab = self._cntwidget(count)
2014-04-17 09:44:26 +02:00
if tab is not None:
2014-09-02 21:54:07 +02:00
if preview:
diag = QPrintPreviewDialog()
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.setWindowFlags(diag.windowFlags() |
Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
2014-09-02 21:54:07 +02:00
diag.paintRequested.connect(tab.print)
diag.exec_()
else:
diag = QPrintDialog()
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.open(lambda: tab.print(diag.printer()))
2014-04-17 09:44:26 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2014-10-06 17:58:40 +02:00
def tab_clone(self, bg=False, window=False):
2014-09-27 23:13:11 +02:00
"""Duplicate the current tab.
Args:
bg: Open in a background tab.
2014-10-06 17:58:40 +02:00
window: Open in a new window.
2014-09-27 23:13:11 +02:00
Return:
The new QWebView.
"""
cmdutils.check_exclusive((bg, window), 'bw')
curtab = self._current_widget()
cur_title = self._tabbed_browser.page_title(self._current_index())
# The new tab could be in a new tabbed_browser (e.g. because of
# tabs-are-windows being set)
if window:
new_tabbed_browser = self._new_tabbed_browser()
else:
new_tabbed_browser = self._tabbed_browser
newtab = new_tabbed_browser.tabopen(background=bg, explicit=True)
new_tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=newtab.win_id)
idx = new_tabbed_browser.indexOf(newtab)
new_tabbed_browser.set_page_title(idx, cur_title)
if config.get('tabs', 'show-favicons'):
new_tabbed_browser.setTabIcon(idx, curtab.icon())
if config.get('tabs', 'tabs-are-windows'):
new_tabbed_browser.window().setWindowIcon(curtab.icon())
newtab.keep_icon = True
2015-01-26 07:20:03 +01:00
newtab.setZoomFactor(curtab.zoomFactor())
2016-06-14 11:03:34 +02:00
newtab.history.deserialize(history = curtab.history.serialize())
2014-09-27 23:13:11 +02:00
2015-03-30 12:14:15 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_detach(self):
"""Detach the current tab to its own window."""
url = self._current_url()
self._open(url, window=True)
cur_widget = self._current_widget()
self._tabbed_browser.close_tab(cur_widget)
2015-03-30 12:14:15 +02:00
2014-10-06 17:58:40 +02:00
def _back_forward(self, tab, bg, window, count, forward):
"""Helper function for :back/:forward."""
2016-06-14 13:28:28 +02:00
history = self._current_widget().history
# Catch common cases before e.g. cloning tab
2016-06-14 13:28:28 +02:00
if not forward and not history.can_go_back():
raise cmdexc.CommandError("At beginning of history.")
2016-06-14 13:28:28 +02:00
elif forward and not history.can_go_forward():
raise cmdexc.CommandError("At end of history.")
2014-10-06 17:58:40 +02:00
if tab or bg or window:
widget = self.tab_clone(bg, window)
else:
widget = self._current_widget()
for _ in range(count):
if forward:
2016-06-14 13:28:28 +02:00
if not history.can_go_forward():
raise cmdexc.CommandError("At end of history.")
2016-06-14 13:28:28 +02:00
history.forward()
else:
2016-06-14 13:28:28 +02:00
if not history.can_go_back():
raise cmdexc.CommandError("At beginning of history.")
2016-06-14 13:28:28 +02:00
history.back()
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def back(self, tab=False, bg=False, window=False, count=1):
2014-04-17 09:44:26 +02:00
"""Go back in the history of the current tab.
Args:
tab: Go back in a new tab.
bg: Go back in a background tab.
2014-10-06 17:58:40 +02:00
window: Go back in a new window.
2014-04-17 09:44:26 +02:00
count: How many pages to go back.
"""
2014-10-06 17:58:40 +02:00
self._back_forward(tab, bg, window, count, forward=False)
2014-04-17 09:44:26 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def forward(self, tab=False, bg=False, window=False, count=1):
2014-04-17 09:44:26 +02:00
"""Go forward in the history of the current tab.
Args:
tab: Go forward in a new tab.
2014-10-06 17:58:40 +02:00
bg: Go forward in a background tab.
window: Go forward in a new window.
2014-04-17 09:44:26 +02:00
count: How many pages to go forward.
"""
2014-10-06 17:58:40 +02:00
self._back_forward(tab, bg, window, count, forward=True)
2014-04-17 09:44:26 +02:00
2014-10-06 17:58:40 +02:00
def _navigate_incdec(self, url, incdec, tab, background, window):
2014-09-22 22:36:31 +02:00
"""Helper method for :navigate when `where' is increment/decrement.
Args:
url: The current url.
incdec: Either 'increment' or 'decrement'.
2014-10-06 17:58:40 +02:00
tab: Whether to open the link in a new tab.
background: Open the link in a new background tab.
window: Open the link in a new window.
2014-09-22 22:36:31 +02:00
"""
segments = set(config.get('general', 'url-incdec-segments'))
2014-09-22 22:36:31 +02:00
try:
new_url = urlutils.incdec_number(url, incdec, segments=segments)
except urlutils.IncDecError as error:
raise cmdexc.CommandError(error.msg)
2016-04-24 16:03:31 +02:00
2014-10-06 17:58:40 +02:00
self._open(new_url, tab, background, window)
2014-09-22 22:36:31 +02:00
2014-10-06 17:58:40 +02:00
def _navigate_up(self, url, tab, background, window):
2014-09-22 22:36:31 +02:00
"""Helper method for :navigate when `where' is up.
Args:
url: The current url.
tab: Whether to open the link in a new tab.
2014-10-06 17:58:40 +02:00
background: Open the link in a new background tab.
window: Open the link in a new window.
2014-09-22 22:36:31 +02:00
"""
path = url.path()
if not path or path == '/':
raise cmdexc.CommandError("Can't go up!")
new_path = posixpath.join(path, posixpath.pardir)
url.setPath(new_path)
2014-10-06 17:58:40 +02:00
self._open(url, tab, background, window)
2014-09-22 22:36:31 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('where', choices=['prev', 'next', 'up', 'increment',
'decrement'])
def navigate(self, where: str, tab=False, bg=False, window=False):
2014-09-22 21:13:42 +02:00
"""Open typical prev/next links or navigate using the URL path.
2014-08-03 00:33:39 +02:00
This tries to automatically click on typical _Previous Page_ or
_Next Page_ links using some heuristics.
2014-09-02 21:54:07 +02:00
2014-09-22 21:13:42 +02:00
Alternatively it can navigate by changing the current URL.
2014-09-02 21:54:07 +02:00
Args:
where: What to open.
2014-05-01 15:27:32 +02:00
- `prev`: Open a _previous_ link.
- `next`: Open a _next_ link.
2014-09-22 21:13:42 +02:00
- `up`: Go up a level in the current URL.
2014-09-22 21:51:09 +02:00
- `increment`: Increment the last number in the URL.
- `decrement`: Decrement the last number in the URL.
2014-08-03 00:33:39 +02:00
2014-09-13 00:22:27 +02:00
tab: Open in a new tab.
2014-10-06 17:58:40 +02:00
bg: Open in a background tab.
window: Open in a new window.
2014-08-03 00:33:39 +02:00
"""
# save the pre-jump position in the special ' mark
self.set_mark("'")
cmdutils.check_exclusive((tab, bg, window), 'tbw')
2014-09-22 21:08:11 +02:00
widget = self._current_widget()
frame = widget.page().currentFrame()
2016-04-25 19:05:20 +02:00
url = self._current_url().adjusted(QUrl.RemoveFragment)
2014-09-22 21:08:11 +02:00
if frame is None:
raise cmdexc.CommandError("No frame focused!")
hintmanager = objreg.get('hintmanager', scope='tab', tab='current')
if where == 'prev':
2014-10-06 17:58:40 +02:00
hintmanager.follow_prevnext(frame, url, prev=True, tab=tab,
2014-10-06 19:53:50 +02:00
background=bg, window=window)
elif where == 'next':
2014-10-06 17:58:40 +02:00
hintmanager.follow_prevnext(frame, url, prev=False, tab=tab,
2014-10-06 19:53:50 +02:00
background=bg, window=window)
2014-09-22 21:13:42 +02:00
elif where == 'up':
2014-10-06 19:53:50 +02:00
self._navigate_up(url, tab, bg, window)
2014-09-22 21:51:09 +02:00
elif where in ('decrement', 'increment'):
2014-10-06 19:53:50 +02:00
self._navigate_incdec(url, where, tab, bg, window)
2015-11-24 21:32:45 +01:00
else: # pragma: no cover
2014-09-22 21:08:11 +02:00
raise ValueError("Got called with invalid value {} for "
"`where'.".format(where))
2014-05-01 15:27:32 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
2016-05-10 22:03:10 +02:00
def scroll_px(self, dx: int, dy: int, count=1):
"""Scroll the current tab by 'count * dx/dy' pixels.
2014-04-17 09:44:26 +02:00
Args:
dx: How much to scroll in x-direction.
2016-04-12 04:31:49 +02:00
dy: How much to scroll in y-direction.
2014-04-17 09:44:26 +02:00
count: multiplier
"""
2014-09-02 21:54:07 +02:00
dx *= count
dy *= count
cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int')
self._current_widget().page().currentFrame().scroll(dx, dy)
2014-04-17 09:44:26 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
def scroll(self, direction: typing.Union[str, int], count=1):
"""Scroll the current tab in the given direction.
Args:
direction: In which direction to scroll
(up/down/left/right/top/bottom).
count: multiplier
"""
fake_keys = {
'up': Qt.Key_Up,
'down': Qt.Key_Down,
'left': Qt.Key_Left,
'right': Qt.Key_Right,
'top': Qt.Key_Home,
'bottom': Qt.Key_End,
'page-up': Qt.Key_PageUp,
'page-down': Qt.Key_PageDown,
}
try:
key = fake_keys[direction]
except KeyError:
expected_values = ', '.join(sorted(fake_keys))
raise cmdexc.CommandError("Invalid value {!r} for direction - "
"expected one of: {}".format(
direction, expected_values))
widget = self._current_widget()
frame = widget.page().currentFrame()
press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, 0, 0, 0)
# Count doesn't make sense with top/bottom
if direction in ('top', 'bottom'):
count = 1
max_min = {
'up': [Qt.Vertical, frame.scrollBarMinimum],
'down': [Qt.Vertical, frame.scrollBarMaximum],
'left': [Qt.Horizontal, frame.scrollBarMinimum],
'right': [Qt.Horizontal, frame.scrollBarMaximum],
'page-up': [Qt.Vertical, frame.scrollBarMinimum],
'page-down': [Qt.Vertical, frame.scrollBarMaximum],
}
for _ in range(count):
# Abort scrolling if the minimum/maximum was reached.
try:
qt_dir, getter = max_min[direction]
except KeyError:
pass
else:
if frame.scrollBarValue(qt_dir) == getter(qt_dir):
return
widget.keyPressEvent(press_evt)
widget.keyReleaseEvent(release_evt)
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('horizontal', flag='x')
2016-05-10 22:03:10 +02:00
def scroll_perc(self, perc: float=None, horizontal=False, count=None):
2014-09-02 21:54:07 +02:00
"""Scroll to a specific percentage of the page.
2014-08-03 00:33:39 +02:00
The percentage can be given either as argument or as count.
If no percentage is given, the page is scrolled to the end.
2014-04-17 09:44:26 +02:00
Args:
perc: Percentage to scroll.
2014-09-13 00:22:27 +02:00
horizontal: Scroll horizontally instead of vertically.
2014-04-17 09:44:26 +02:00
count: Percentage to scroll.
"""
# save the pre-jump position in the special ' mark
self.set_mark("'")
if perc is None and count is None:
perc = 100
2016-06-29 00:12:44 +02:00
elif count is not None:
perc = count
orientation = Qt.Horizontal if horizontal else Qt.Vertical
if perc == 0 and orientation == Qt.Vertical:
self.scroll('top')
elif perc == 100 and orientation == Qt.Vertical:
self.scroll('bottom')
else:
perc = qtutils.check_overflow(perc, 'int', fatal=False)
frame = self._current_widget().page().currentFrame()
m = frame.scrollBarMaximum(orientation)
if m == 0:
return
frame.setScrollBarValue(orientation, int(m * perc / 100))
2014-04-17 09:44:26 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('top_navigate', metavar='ACTION',
choices=('prev', 'decrement'))
@cmdutils.argument('bottom_navigate', metavar='ACTION',
choices=('next', 'increment'))
2016-05-10 22:03:10 +02:00
def scroll_page(self, x: float, y: float, *,
top_navigate: str=None, bottom_navigate: str=None,
count=1):
2014-04-17 09:44:26 +02:00
"""Scroll the frame page-wise.
Args:
2014-08-03 00:33:39 +02:00
x: How many pages to scroll to the right.
y: How many pages to scroll down.
2015-05-31 15:02:09 +02:00
bottom_navigate: :navigate action (next, increment) to run when
scrolling down at the bottom of the page.
top_navigate: :navigate action (prev, decrement) to run when
scrolling up at the top of the page.
2014-04-17 09:44:26 +02:00
count: multiplier
"""
frame = self._current_widget().page().currentFrame()
2015-05-31 15:11:37 +02:00
if not frame.url().isValid():
# See https://github.com/The-Compiler/qutebrowser/issues/701
2015-05-29 23:49:48 +02:00
return
if (bottom_navigate is not None and
frame.scrollPosition().y() >=
frame.scrollBarMaximum(Qt.Vertical)):
self.navigate(bottom_navigate)
return
elif top_navigate is not None and frame.scrollPosition().y() == 0:
self.navigate(top_navigate)
return
mult_x = count * x
mult_y = count * y
if mult_y.is_integer():
if mult_y == 0:
pass
elif mult_y < 0:
2015-05-15 21:32:42 +02:00
self.scroll('page-up', count=-int(mult_y))
elif mult_y > 0: # pragma: no branch
2015-05-15 21:32:42 +02:00
self.scroll('page-down', count=int(mult_y))
mult_y = 0
if mult_x == 0 and mult_y == 0:
return
size = frame.geometry()
dx = mult_x * size.width()
dy = mult_y * size.height()
cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int')
frame.scroll(dx, dy)
2014-04-17 09:44:26 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False, domain=False, pretty=False):
2014-09-02 21:54:07 +02:00
"""Yank the current URL/title to the clipboard or primary selection.
2014-04-17 09:44:26 +02:00
2014-05-18 08:14:11 +02:00
Args:
2014-09-13 00:22:27 +02:00
sel: Use the primary selection instead of the clipboard.
title: Yank the title instead of the URL.
2015-06-04 13:20:39 +02:00
domain: Yank only the scheme, domain, and port number.
pretty: Yank the URL in pretty decoded form.
2014-05-18 08:14:11 +02:00
"""
2014-09-02 21:54:07 +02:00
if title:
s = self._tabbed_browser.page_title(self._current_index())
what = 'title'
elif domain:
2015-06-04 13:20:39 +02:00
port = self._current_url().port()
s = '{}://{}{}'.format(self._current_url().scheme(),
self._current_url().host(),
':' + str(port) if port > -1 else '')
what = 'domain'
2014-05-19 11:56:51 +02:00
else:
2016-04-20 16:25:26 +02:00
flags = QUrl.RemovePassword
if not pretty:
flags |= QUrl.FullyEncoded
s = self._current_url().toString(flags)
what = 'URL'
2016-05-08 22:06:00 +02:00
if sel and utils.supports_selection():
2014-05-19 11:56:51 +02:00
target = "primary selection"
else:
sel = False
2014-05-19 11:56:51 +02:00
target = "clipboard"
utils.set_clipboard(s, selection=sel)
message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s))
2014-04-17 09:44:26 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_in(self, count=1):
"""Increase the zoom level for the current tab.
2014-04-17 09:44:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
count: How many steps to zoom in.
2014-04-17 09:44:26 +02:00
"""
tab = self._current_widget()
try:
perc = tab.zoom(count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
2014-04-17 09:44:26 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_out(self, count=1):
"""Decrease the zoom level for the current tab.
2014-04-17 09:44:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
count: How many steps to zoom out.
2014-04-17 09:44:26 +02:00
"""
tab = self._current_widget()
try:
perc = tab.zoom(-count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
2014-04-29 18:00:22 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
2016-05-10 22:03:10 +02:00
def zoom(self, zoom: int=None, count=None):
2014-08-03 00:33:39 +02:00
"""Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither of both is
given, the zoom is set to the default zoom.
2014-05-09 14:20:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
zoom: The zoom percentage to set.
count: The zoom percentage to set.
2014-05-09 14:20:26 +02:00
"""
2014-05-09 19:12:08 +02:00
try:
default = config.get('ui', 'default-zoom')
level = cmdutils.arg_or_count(zoom, count, default=default)
2014-05-09 19:12:08 +02:00
except ValueError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
tab = self._current_widget()
try:
tab.zoom_perc(level)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(level))
2014-05-09 14:20:26 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self, left=False, right=False):
"""Close all tabs except for the current one.
Args:
left: Keep tabs to the left of the current.
right: Keep tabs to the right of the current.
"""
cmdutils.check_exclusive((left, right), 'lr')
cur_idx = self._tabbed_browser.currentIndex()
assert cur_idx != -1
for i, tab in enumerate(self._tabbed_browser.widgets()):
if (i == cur_idx or (left and i < cur_idx) or
(right and i > cur_idx)):
2014-05-17 22:38:07 +02:00
continue
else:
self._tabbed_browser.close_tab(tab)
2014-05-17 22:38:07 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2014-05-17 23:22:10 +02:00
def undo(self):
2014-08-03 00:33:39 +02:00
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
try:
self._tabbed_browser.undo()
except IndexError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Nothing to undo!")
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_prev(self, count=1):
2014-08-03 00:33:39 +02:00
"""Switch to the previous tab, or switch [count] tabs back.
2014-05-17 22:38:07 +02:00
Args:
count: How many tabs to switch back.
"""
if self._count() == 0:
# Running :tab-prev after last tab was closed
# See https://github.com/The-Compiler/qutebrowser/issues/1448
return
newidx = self._current_index() - count
2014-05-17 22:38:07 +02:00
if newidx >= 0:
self._set_current_index(newidx)
2014-08-06 08:10:32 +02:00
elif config.get('tabs', 'wrap'):
self._set_current_index(newidx % self._count())
2014-05-17 22:38:07 +02:00
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("First tab")
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_next(self, count=1):
2014-08-03 00:33:39 +02:00
"""Switch to the next tab, or switch [count] tabs forward.
2014-05-17 22:38:07 +02:00
Args:
count: How many tabs to switch forward.
"""
if self._count() == 0:
# Running :tab-next after last tab was closed
# See https://github.com/The-Compiler/qutebrowser/issues/1448
return
newidx = self._current_index() + count
if newidx < self._count():
self._set_current_index(newidx)
2014-08-06 08:10:32 +02:00
elif config.get('tabs', 'wrap'):
self._set_current_index(newidx % self._count())
2014-05-17 22:38:07 +02:00
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Last tab")
2014-05-17 22:38:07 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def paste(self, sel=False, tab=False, bg=False, window=False):
2014-05-17 22:38:07 +02:00
"""Open a page from the clipboard.
2016-01-13 18:13:30 +01:00
If the pasted text contains newlines, each line gets opened in its own
tab.
2014-05-17 22:38:07 +02:00
Args:
2014-09-13 00:22:27 +02:00
sel: Use the primary selection instead of the clipboard.
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in new window.
2014-05-17 22:38:07 +02:00
"""
force_search = False
2016-05-08 22:06:00 +02:00
if sel and utils.supports_selection():
target = "Primary selection"
else:
sel = False
target = "Clipboard"
text = utils.get_clipboard(selection=sel)
2016-02-02 06:37:49 +01:00
if not text.strip():
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("{} is empty.".format(target))
log.misc.debug("{} contained: {!r}".format(target, text))
text_urls = [u for u in text.split('\n') if u.strip()]
2016-02-28 23:41:20 +01:00
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
urlutils.get_path_if_valid(
text_urls[0], check_exists=True) is None):
force_search = True
text_urls = [text]
for i, text_url in enumerate(text_urls):
if not window and i > 0:
tab = False
bg = True
try:
url = urlutils.fuzzy_url(text_url, force_search=force_search)
except urlutils.InvalidUrlError as e:
raise cmdexc.CommandError(e)
self._open(url, tab, bg, window)
2014-05-17 22:38:07 +02:00
2016-05-10 23:04:52 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', completion=usertypes.Completion.tab)
def buffer(self, index):
"""Select tab by index or url/title best match.
Focuses window if necessary.
Args:
index: The [win_id/]index of the tab to focus. Or a substring
in which case the closest match will be focused.
"""
index_parts = index.split('/', 1)
try:
for part in index_parts:
int(part)
except ValueError:
model = instances.get(usertypes.Completion.tab)
sf = sortfilter.CompletionFilterModel(source=model)
sf.set_pattern(index)
if sf.count() > 0:
index = sf.data(sf.first_item())
index_parts = index.split('/', 1)
else:
raise cmdexc.CommandError(
"No matching tab for: {}".format(index))
if len(index_parts) == 2:
win_id = int(index_parts[0])
idx = int(index_parts[1])
elif len(index_parts) == 1:
idx = int(index_parts[0])
active_win = objreg.get('app').activeWindow()
if active_win is None:
# Not sure how you enter a command without an active window...
raise cmdexc.CommandError(
"No window specified and couldn't find active window!")
win_id = active_win.win_id
if win_id not in objreg.window_registry:
raise cmdexc.CommandError(
"There's no window with id {}!".format(win_id))
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if not 0 < idx <= tabbed_browser.count():
raise cmdexc.CommandError(
"There's no tab with index {}!".format(idx))
window = objreg.window_registry[win_id]
window.activateWindow()
window.raise_()
tabbed_browser.setCurrentIndex(idx-1)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last'])
@cmdutils.argument('count', count=True)
def tab_focus(self, index: typing.Union[str, int]=None, count=None):
2014-05-17 22:38:07 +02:00
"""Select the tab given as argument/[count].
2015-08-11 22:01:18 +02:00
If neither count nor index are given, it behaves like tab-next.
2014-05-17 22:38:07 +02:00
Args:
2014-08-03 00:39:39 +02:00
index: The tab index to focus, starting with 1. The special value
`last` focuses the last focused tab. Negative indexes
counts from the end, such that -1 is the last tab.
2014-08-03 00:33:39 +02:00
count: The tab index to focus, starting with 1.
2014-05-17 22:38:07 +02:00
"""
2014-08-03 00:39:39 +02:00
if index == 'last':
self._tab_focus_last()
return
if index is None and count is None:
self.tab_next()
return
if index is not None and index < 0:
index = self._count() + index + 1
2014-05-17 22:38:07 +02:00
try:
idx = cmdutils.arg_or_count(index, count, default=1,
countzero=self._count())
2014-05-17 22:38:07 +02:00
except ValueError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
2014-05-17 22:38:07 +02:00
cmdutils.check_overflow(idx + 1, 'int')
if 1 <= idx <= self._count():
self._set_current_index(idx - 1)
2014-05-17 22:38:07 +02:00
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("There's no tab with index {}!".format(
idx))
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('direction', choices=['+', '-'])
@cmdutils.argument('count', count=True)
def tab_move(self, direction: str=None, count=None):
2014-05-17 22:38:07 +02:00
"""Move the current tab.
Args:
2014-09-13 00:22:27 +02:00
direction: `+` or `-` for relative moving, not given for absolute
moving.
2014-08-03 00:33:39 +02:00
count: If moving absolutely: New position (default: 0)
2014-05-17 22:38:07 +02:00
If moving relatively: Offset.
"""
if direction is None:
2015-11-26 17:44:34 +01:00
# absolute moving
new_idx = 0 if count is None else count - 1
2014-05-17 22:38:07 +02:00
elif direction in '+-':
2015-11-26 17:44:34 +01:00
# relative moving
delta = 1 if count is None else count
if direction == '-':
new_idx = self._current_index() - delta
2015-11-26 18:37:47 +01:00
elif direction == '+': # pragma: no branch
2015-11-26 17:44:34 +01:00
new_idx = self._current_index() + delta
2015-12-02 06:20:41 +01:00
if config.get('tabs', 'wrap'):
2015-12-02 06:20:41 +01:00
new_idx %= self._count()
else: # pragma: no cover
raise ValueError("Invalid direction '{}'!".format(direction))
2015-11-26 17:44:34 +01:00
if not 0 <= new_idx < self._count():
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Can't move tab to position {}!".format(
new_idx + 1))
2015-11-26 17:44:34 +01:00
tab = self._current_widget()
cur_idx = self._current_index()
icon = self._tabbed_browser.tabIcon(cur_idx)
label = self._tabbed_browser.page_title(cur_idx)
2014-05-17 22:38:07 +02:00
cmdutils.check_overflow(cur_idx, 'int')
cmdutils.check_overflow(new_idx, 'int')
self._tabbed_browser.setUpdatesEnabled(False)
try:
2016-06-06 16:49:29 +02:00
color = self._tabbed_browser.tabBar().tab_data(
cur_idx, 'indicator-color')
self._tabbed_browser.removeTab(cur_idx)
self._tabbed_browser.insertTab(new_idx, tab, icon, label)
self._set_current_index(new_idx)
2016-06-06 16:49:29 +02:00
self._tabbed_browser.set_tab_indicator_color(new_idx, color)
finally:
self._tabbed_browser.setUpdatesEnabled(True)
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
def spawn(self, cmdline, userscript=False, verbose=False, detach=False):
"""Spawn a command in a shell.
2014-05-03 14:25:22 +02:00
2016-04-20 18:39:18 +02:00
Note the `{url}` and `{url:pretty}` variables might be useful here.
`{url}` gets replaced by the URL in fully encoded format and
`{url:pretty}` uses a "pretty form" with most percent-encoded
characters decoded.
2014-05-03 14:25:22 +02:00
Args:
2016-03-25 11:09:03 +01:00
userscript: Run the command as a userscript. You can use an
absolute path, or store the userscript in one of those
locations:
- `~/.local/share/qutebrowser/userscripts`
(or `$XDG_DATA_DIR`)
- `/usr/share/qutebrowser/userscripts`
verbose: Show notifications when the command started/exited.
2015-05-31 16:25:25 +02:00
detach: Whether the command should be detached from qutebrowser.
cmdline: The commandline to execute.
2014-05-03 14:25:22 +02:00
"""
try:
cmd, *args = shlex.split(cmdline)
except ValueError as e:
raise cmdexc.CommandError("Error while splitting command: "
"{}".format(e))
args = runners.replace_variables(self._win_id, args)
log.procs.debug("Executing {} with args {}, userscript={}".format(
cmd, args, userscript))
if userscript:
2015-08-27 20:28:16 +02:00
# ~ expansion is handled by the userscript module.
self._run_userscript(cmd, *args, verbose=verbose)
else:
2015-08-27 20:28:16 +02:00
cmd = os.path.expanduser(cmd)
proc = guiprocess.GUIProcess(self._win_id, what='command',
verbose=verbose,
parent=self._tabbed_browser)
2015-05-31 16:25:25 +02:00
if detach:
proc.start_detached(cmd, args)
2015-05-31 16:25:25 +02:00
else:
proc.start(cmd, args)
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2014-05-09 13:09:37 +02:00
def home(self):
"""Open main startpage in current tab."""
self.openurl(config.get('general', 'startpage')[0])
def _run_userscript(self, cmd, *args, verbose=False):
2015-06-29 17:48:30 +02:00
"""Run a userscript given as argument.
2014-07-29 01:45:42 +02:00
Args:
cmd: The userscript to run.
args: Arguments to pass to the userscript.
verbose: Show notifications when the command started/exited.
2014-07-29 01:45:42 +02:00
"""
env = {
'QUTE_MODE': 'command',
}
idx = self._current_index()
if idx != -1:
env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx)
webview = self._tabbed_browser.currentWidget()
if webview is None:
mainframe = None
else:
if webview.hasSelection():
env['QUTE_SELECTED_TEXT'] = webview.selectedText()
env['QUTE_SELECTED_HTML'] = webview.selectedHtml()
mainframe = webview.page().mainFrame()
try:
url = self._tabbed_browser.current_url()
except qtutils.QtValueError:
pass
else:
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
env.update(userscripts.store_source(mainframe))
userscripts.run(cmd, *args, win_id=self._win_id, env=env,
verbose=verbose)
2014-05-21 19:53:58 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2014-05-22 16:44:47 +02:00
def quickmark_save(self):
"""Save the current page as a quickmark."""
quickmark_manager = objreg.get('quickmark-manager')
quickmark_manager.prompt_save(self._win_id, self._current_url())
2014-05-22 16:44:47 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window',
2016-05-10 23:04:52 +02:00
maxsplit=0)
2016-05-10 23:35:37 +02:00
@cmdutils.argument('name',
completion=usertypes.Completion.quickmark_by_name)
def quickmark_load(self, name, tab=False, bg=False, window=False):
2014-09-07 21:03:20 +02:00
"""Load a quickmark.
Args:
name: The name of the quickmark to load.
2014-09-13 00:22:27 +02:00
tab: Load the quickmark in a new tab.
bg: Load the quickmark in a new background tab.
window: Load the quickmark in a new window.
2014-09-07 21:03:20 +02:00
"""
try:
url = objreg.get('quickmark-manager').get(name)
except urlmarks.Error as e:
raise cmdexc.CommandError(str(e))
self._open(url, tab, bg, window)
2014-05-22 16:44:47 +02:00
2015-05-22 00:17:22 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2015-06-01 23:52:23 +02:00
def bookmark_add(self):
2015-05-22 00:17:22 +02:00
"""Save the current page as a bookmark."""
bookmark_manager = objreg.get('bookmark-manager')
url = self._current_url()
try:
bookmark_manager.add(url, self._current_title())
except urlmarks.Error as e:
raise cmdexc.CommandError(str(e))
else:
message.info(self._win_id,
"Bookmarked {}!".format(url.toDisplayString()))
2015-05-22 00:17:22 +02:00
2015-05-25 01:07:57 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window',
2016-05-10 23:04:52 +02:00
maxsplit=0)
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
2015-06-01 23:52:23 +02:00
def bookmark_load(self, url, tab=False, bg=False, window=False):
2015-05-25 01:07:57 +02:00
"""Load a bookmark.
Args:
url: The url of the bookmark to load.
tab: Load the bookmark in a new tab.
bg: Load the bookmark in a new background tab.
window: Load the bookmark in a new window.
"""
try:
url = urlutils.fuzzy_url(url)
except urlutils.InvalidUrlError as e:
raise cmdexc.CommandError(e)
self._open(url, tab, bg, window)
2015-05-25 01:07:57 +02:00
2015-05-31 18:50:28 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
2015-05-31 18:50:28 +02:00
def follow_selected(self, tab=False):
"""Follow the selected text.
Args:
tab: Load the selected link in a new tab.
"""
2015-05-30 16:37:25 +02:00
widget = self._current_widget()
2015-05-31 18:18:27 +02:00
page = widget.page()
if not page.hasSelection():
return
if QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled):
if tab:
2015-05-31 18:18:27 +02:00
page.open_target = usertypes.ClickTarget.tab
page.currentFrame().evaluateJavaScript(
'window.getSelection().anchorNode.parentNode.click()')
else:
2015-05-31 00:03:39 +02:00
try:
2015-05-31 17:56:27 +02:00
selected_element = xml.etree.ElementTree.fromstring(
2015-05-31 00:03:39 +02:00
'<html>' + widget.selectedHtml() + '</html>').find('a')
2015-05-31 17:56:27 +02:00
except xml.etree.ElementTree.ParseError:
2015-05-31 18:56:08 +02:00
raise cmdexc.CommandError('Could not parse selected element!')
if selected_element is not None:
try:
url = selected_element.attrib['href']
except KeyError:
2015-05-31 18:56:08 +02:00
raise cmdexc.CommandError('Anchor element without href!')
2015-05-31 18:13:37 +02:00
url = self._current_url().resolved(QUrl(url))
self._open(url, tab)
2015-05-30 16:37:25 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', name='inspector',
scope='window')
def toggle_inspector(self):
"""Toggle the web inspector.
Note: Due a bug in Qt, the inspector will show incorrect request
headers in the network tab.
"""
cur = self._current_widget()
if cur.inspector is None:
2014-06-03 20:28:51 +02:00
if not config.get('general', 'developer-extras'):
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(
"Please enable developer-extras before using the "
"webinspector!")
cur.inspector = inspector.WebInspector()
2014-05-27 16:04:45 +02:00
cur.inspector.setPage(cur.page())
cur.inspector.show()
elif cur.inspector.isVisible():
cur.inspector.hide()
else:
2014-06-03 20:28:51 +02:00
if not config.get('general', 'developer-extras'):
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(
"Please enable developer-extras before using the "
"webinspector!")
else:
cur.inspector.show()
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('dest_old', hide=True)
def download(self, url=None, dest_old=None, *, mhtml_=False, dest=None):
2015-02-09 12:06:49 +01:00
"""Download a given URL, or current page if no URL given.
The form `:download [url] [dest]` is deprecated, use `:download --dest
[dest] [url]` instead.
Args:
2015-02-09 17:38:50 +01:00
url: The URL to download. If not given, download the current page.
dest_old: (deprecated) Same as dest.
dest: The file path to write the download to, or None to ask.
2015-10-07 23:53:03 +02:00
mhtml_: Download the current page and all assets as mhtml file.
"""
if dest_old is not None:
2016-04-27 18:30:54 +02:00
message.warning(self._win_id,
":download [url] [dest] is deprecated - use"
" download --dest [dest] [url]")
if dest is not None:
raise cmdexc.CommandError("Can't give two destinations for the"
" download.")
dest = dest_old
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
2015-02-09 12:06:49 +01:00
if url:
2015-10-07 23:53:03 +02:00
if mhtml_:
raise cmdexc.CommandError("Can only download the current page"
" as mhtml.")
url = urlutils.qurl_from_user_input(url)
urlutils.raise_cmdexc_if_invalid(url)
download_manager.get(url, filename=dest)
else:
2015-10-07 23:53:03 +02:00
if mhtml_:
self._download_mhtml(dest)
else:
page = self._current_widget().page()
2015-10-07 23:53:03 +02:00
download_manager.get(self._current_url(), page=page,
filename=dest)
2014-06-19 17:58:46 +02:00
def _download_mhtml(self, dest=None):
"""Download the current page as an MHTML file, including all assets.
Args:
dest: The file path to write the download to.
"""
2015-11-21 00:10:49 +01:00
web_view = self._current_widget()
2015-09-22 13:37:03 +02:00
if dest is None:
suggested_fn = self._current_title() + ".mht"
suggested_fn = utils.sanitize_filename(suggested_fn)
filename, q = downloads.ask_for_filename(
2015-11-21 00:10:49 +01:00
suggested_fn, self._win_id, parent=web_view,
)
if filename is not None:
2015-11-21 00:10:49 +01:00
mhtml.start_download_checked(filename, web_view=web_view)
else:
q.answered.connect(functools.partial(
2015-11-21 00:10:49 +01:00
mhtml.start_download_checked, web_view=web_view))
q.ask()
2015-09-22 13:37:03 +02:00
else:
2015-11-21 00:10:49 +01:00
mhtml.start_download_checked(dest, web_view=web_view)
2014-06-19 17:58:46 +02:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
2014-09-15 17:59:54 +02:00
def view_source(self):
"""Show the source of the current page."""
2014-09-15 22:01:13 +02:00
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
2014-09-15 17:59:54 +02:00
widget = self._current_widget()
if widget.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
frame = widget.page().currentFrame()
html = frame.toHtml()
lexer = pygments.lexers.HtmlLexer()
2016-04-27 18:30:54 +02:00
formatter = pygments.formatters.HtmlFormatter(full=True,
linenos='table')
2014-09-15 17:59:54 +02:00
highlighted = pygments.highlight(html, lexer, formatter)
current_url = self._current_url()
tab = self._tabbed_browser.tabopen(explicit=True)
tab.setHtml(highlighted, current_url)
2014-09-15 17:59:54 +02:00
tab.viewing_source = True
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
def debug_dump_page(self, dest, plain=False):
"""Dump the current page's content to a file.
Args:
dest: Where to write the file to.
plain: Write plain text instead of HTML.
"""
2016-06-14 13:26:30 +02:00
tab = self._current_widget()
2015-11-24 18:04:10 +01:00
dest = os.path.expanduser(dest)
2016-06-14 13:26:30 +02:00
def callback(data):
try:
with open(dest, 'w', encoding='utf-8') as f:
f.write(data)
except OSError as e:
message.error(self._win_id, 'Could not write page: {}'.format(e))
else:
message.info(self._win_id, "Dumped page to {}.".format(dest))
tab.dump_async(callback, plain=plain)
@cmdutils.register(instance='command-dispatcher', name='help',
2014-09-28 22:13:14 +02:00
scope='window')
2016-05-10 23:04:52 +02:00
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
2014-10-06 20:40:00 +02:00
def show_help(self, tab=False, bg=False, window=False, topic=None):
2014-09-08 07:44:32 +02:00
r"""Show help about a command or setting.
2014-09-08 06:57:22 +02:00
Args:
2014-10-06 20:40:00 +02:00
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
2014-09-08 06:57:22 +02:00
topic: The topic to show help for.
- :__command__ for commands.
- __section__\->__option__ for settings.
"""
2014-09-08 12:18:54 +02:00
if topic is None:
path = 'index.html'
elif topic.startswith(':'):
2014-09-08 06:57:22 +02:00
command = topic[1:]
if command not in cmdutils.cmd_dict:
raise cmdexc.CommandError("Invalid command {}!".format(
command))
path = 'commands.html#{}'.format(command)
elif '->' in topic:
parts = topic.split('->')
if len(parts) != 2:
raise cmdexc.CommandError("Invalid help topic {}!".format(
topic))
try:
config.get(*parts)
except configexc.NoSectionError:
2014-09-08 06:57:22 +02:00
raise cmdexc.CommandError("Invalid section {}!".format(
parts[0]))
except configexc.NoOptionError:
2014-09-08 06:57:22 +02:00
raise cmdexc.CommandError("Invalid option {}!".format(
parts[1]))
path = 'settings.html#{}'.format(topic.replace('->', '-'))
else:
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
2014-10-06 20:40:00 +02:00
url = QUrl('qute://help/{}'.format(path))
self._open(url, tab, bg, window)
2014-09-08 06:57:22 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def messages(self, level='error', plain=False, tab=False, bg=False,
window=False):
"""Show a log of past messages.
Args:
level: Include messages with `level` or higher severity.
Valid values: vdebug, debug, info, warning, error, critical.
plain: Whether to show plaintext (as opposed to html).
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
"""
if level.upper() not in log.LOG_LEVELS:
raise cmdexc.CommandError("Invalid log level {}!".format(level))
if plain:
url = QUrl('qute://plainlog?level={}'.format(level))
else:
url = QUrl('qute://log?level={}'.format(level))
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher',
2015-04-28 16:50:42 +02:00
modes=[KeyMode.insert], hide=True, scope='window')
2014-05-17 23:22:10 +02:00
def open_editor(self):
2014-08-03 00:33:39 +02:00
"""Open an external editor with the currently selected form field.
The editor which should be launched can be configured via the
`general -> editor` config option.
"""
frame = self._current_widget().page().currentFrame()
2014-09-04 08:00:05 +02:00
try:
elem = webelem.focus_elem(frame)
except webelem.IsNullError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No element focused!")
2014-09-04 08:00:05 +02:00
if not elem.is_editable(strict=True):
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Focused element is not editable!")
2014-09-04 08:00:05 +02:00
if elem.is_content_editable():
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
ed.editing_finished.connect(functools.partial(
self.on_editing_finished, elem))
ed.edit(text)
def on_editing_finished(self, elem, text):
2014-04-30 10:46:20 +02:00
"""Write the editor text into the form field and clean up tempfile.
2014-04-29 18:00:22 +02:00
Callback for GUIProcess when the editor was closed.
Args:
2014-09-04 08:00:05 +02:00
elem: The WebElementWrapper which was modified.
text: The new text to insert.
2014-04-29 18:00:22 +02:00
"""
2014-09-04 08:00:05 +02:00
try:
if elem.is_content_editable():
log.misc.debug("Filling element {} via setPlainText.".format(
elem.debug_text()))
elem.setPlainText(text)
else:
log.misc.debug("Filling element {} via javascript.".format(
elem.debug_text()))
text = webelem.javascript_escape(text)
elem.evaluateJavaScript("this.value='{}'".format(text))
except webelem.IsNullError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Element vanished while editing!")
@cmdutils.register(instance='command-dispatcher',
2016-01-27 10:04:24 +01:00
modes=[KeyMode.insert], hide=True, scope='window',
needs_js=True)
def paste_primary(self):
2016-01-30 14:13:41 +01:00
"""Paste the primary selection at cursor position."""
frame = self._current_widget().page().currentFrame()
try:
elem = webelem.focus_elem(frame)
except webelem.IsNullError:
raise cmdexc.CommandError("No element focused!")
if not elem.is_editable(strict=True):
raise cmdexc.CommandError("Focused element is not editable!")
try:
sel = utils.get_clipboard(selection=True)
except utils.SelectionUnsupportedError:
sel = utils.get_clipboard()
log.misc.debug("Pasting primary selection into element {}".format(
elem.debug_text()))
elem.evaluateJavaScript("""
var sel = '{}';
var event = document.createEvent('TextEvent');
event.initTextEvent('textInput', true, true, null, sel);
this.dispatchEvent(event);
""".format(webelem.javascript_escape(sel)))
2015-09-18 06:55:17 +02:00
def _clear_search(self, view, text):
"""Clear search string/highlights for the given view.
This does nothing if the view's search text is the same as the given
text.
"""
if view.search_text is not None and view.search_text != text:
# We first clear the marked text, then the highlights
view.search('', 0)
view.search('', QWebPage.HighlightAllOccurrences)
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
def search(self, text="", reverse=False):
"""Search for a text on the current page. With no text, clear results.
Args:
text: The text to search for.
reverse: Reverse search direction.
"""
self.set_mark("'")
view = self._current_widget()
2015-09-18 06:55:17 +02:00
self._clear_search(view, text)
flags = 0
ignore_case = config.get('general', 'ignore-case')
if ignore_case == 'smart':
if not text.islower():
flags |= QWebPage.FindCaseSensitively
elif not ignore_case:
flags |= QWebPage.FindCaseSensitively
if config.get('general', 'wrap-search'):
flags |= QWebPage.FindWrapsAroundDocument
if reverse:
flags |= QWebPage.FindBackward
# We actually search *twice* - once to highlight everything, then again
# to get a mark so we can navigate.
view.search(text, flags)
view.search(text, flags | QWebPage.HighlightAllOccurrences)
view.search_text = text
view.search_flags = flags
self._tabbed_browser.search_text = text
self._tabbed_browser.search_flags = flags
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
def search_next(self, count=1):
"""Continue the search to the ([count]th) next term.
Args:
count: How many elements to ignore.
"""
self.set_mark("'")
view = self._current_widget()
self._clear_search(view, self._tabbed_browser.search_text)
if self._tabbed_browser.search_text is not None:
view.search_text = self._tabbed_browser.search_text
view.search_flags = self._tabbed_browser.search_flags
view.search(view.search_text,
view.search_flags | QWebPage.HighlightAllOccurrences)
for _ in range(count):
view.search(view.search_text, view.search_flags)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@cmdutils.argument('count', count=True)
def search_prev(self, count=1):
"""Continue the search to the ([count]th) previous term.
Args:
count: How many elements to ignore.
"""
self.set_mark("'")
view = self._current_widget()
self._clear_search(view, self._tabbed_browser.search_text)
if self._tabbed_browser.search_text is not None:
view.search_text = self._tabbed_browser.search_text
view.search_flags = self._tabbed_browser.search_flags
view.search(view.search_text,
view.search_flags | QWebPage.HighlightAllOccurrences)
# The int() here serves as a QFlags constructor to create a copy of the
# QFlags instance rather as a reference. I don't know why it works this
# way, but it does.
flags = int(view.search_flags)
if flags & QWebPage.FindBackward:
flags &= ~QWebPage.FindBackward
else:
flags |= QWebPage.FindBackward
for _ in range(count):
view.search(view.search_text, flags)
2015-04-22 07:13:56 +02:00
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_line(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the next line.
Args:
count: How many lines to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToNextLine
else:
act = QWebPage.SelectNextLine
for _ in range(count):
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_line(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the prev line.
Args:
count: How many lines to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToPreviousLine
else:
act = QWebPage.SelectPreviousLine
for _ in range(count):
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_char(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the next char.
Args:
count: How many lines to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToNextChar
else:
act = QWebPage.SelectNextChar
for _ in range(count):
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_char(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the previous char.
Args:
count: How many chars to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToPreviousChar
else:
act = QWebPage.SelectPreviousChar
for _ in range(count):
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_word(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the end of the word.
Args:
count: How many words to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
2015-12-01 11:19:13 +01:00
act = [QWebPage.MoveToNextWord]
if sys.platform == 'win32': # pragma: no cover
2015-12-01 11:19:13 +01:00
act.append(QWebPage.MoveToPreviousChar)
else:
2015-12-01 11:19:13 +01:00
act = [QWebPage.SelectNextWord]
if sys.platform == 'win32': # pragma: no cover
2015-12-01 11:19:13 +01:00
act.append(QWebPage.SelectPreviousChar)
for _ in range(count):
2015-12-01 11:19:13 +01:00
for a in act:
webview.triggerPageAction(a)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_word(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the next word.
Args:
count: How many words to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
2015-12-01 11:53:07 +01:00
act = [QWebPage.MoveToNextWord]
if sys.platform != 'win32': # pragma: no branch
2015-12-01 11:53:07 +01:00
act.append(QWebPage.MoveToNextChar)
else:
2015-12-01 11:53:07 +01:00
act = [QWebPage.SelectNextWord]
if sys.platform != 'win32': # pragma: no branch
2015-12-01 11:53:07 +01:00
act.append(QWebPage.SelectNextChar)
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_word(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the previous word.
Args:
count: How many words to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToPreviousWord
else:
act = QWebPage.SelectPreviousWord
for _ in range(count):
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
2015-05-18 22:28:11 +02:00
modes=[KeyMode.caret], scope='window')
def move_to_start_of_line(self):
2015-05-18 22:27:44 +02:00
"""Move the cursor or selection to the start of the line."""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToStartOfLine
else:
act = QWebPage.SelectStartOfLine
2015-05-18 22:28:11 +02:00
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
2015-05-18 22:28:11 +02:00
modes=[KeyMode.caret], scope='window')
def move_to_end_of_line(self):
2015-05-18 22:27:44 +02:00
"""Move the cursor or selection to the end of line."""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToEndOfLine
else:
act = QWebPage.SelectEndOfLine
2015-05-18 22:28:11 +02:00
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_start_of_next_block(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the start of next block.
Args:
count: How many blocks to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToNextLine,
2015-05-11 20:32:27 +02:00
QWebPage.MoveToStartOfBlock]
else:
act = [QWebPage.SelectNextLine,
2015-05-11 20:32:27 +02:00
QWebPage.SelectStartOfBlock]
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_start_of_prev_block(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the start of previous block.
Args:
count: How many blocks to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToPreviousLine,
2015-05-11 20:32:27 +02:00
QWebPage.MoveToStartOfBlock]
else:
act = [QWebPage.SelectPreviousLine,
2015-05-11 20:32:27 +02:00
QWebPage.SelectStartOfBlock]
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_next_block(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the end of next block.
Args:
count: How many blocks to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToNextLine,
2015-05-11 20:32:27 +02:00
QWebPage.MoveToEndOfBlock]
else:
act = [QWebPage.SelectNextLine,
2015-05-11 20:32:27 +02:00
QWebPage.SelectEndOfBlock]
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_prev_block(self, count=1):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the end of previous block.
Args:
count: How many blocks to move.
"""
webview = self._current_widget()
if not webview.selection_enabled:
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
else:
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
for _ in range(count):
for a in act:
webview.triggerPageAction(a)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
def move_to_start_of_document(self):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the start of the document."""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToStartOfDocument
else:
act = QWebPage.SelectStartOfDocument
webview.triggerPageAction(act)
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
def move_to_end_of_document(self):
2015-05-11 22:29:44 +02:00
"""Move the cursor or selection to the end of the document."""
webview = self._current_widget()
if not webview.selection_enabled:
act = QWebPage.MoveToEndOfDocument
else:
act = QWebPage.SelectEndOfDocument
webview.triggerPageAction(act)
2015-07-16 13:57:53 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank_selected(self, sel=False, keep=False):
2015-05-11 22:29:44 +02:00
"""Yank the selected text to the clipboard or primary selection.
Args:
sel: Use the primary selection instead of the clipboard.
keep: If given, stay in visual mode after yanking.
"""
s = self._current_widget().selectedText()
if not self._current_widget().hasSelection() or len(s) == 0:
message.info(self._win_id, "Nothing to yank")
return
2016-05-08 22:06:00 +02:00
if sel and utils.supports_selection():
target = "primary selection"
else:
sel = False
target = "clipboard"
utils.set_clipboard(s, sel)
2015-04-28 16:50:42 +02:00
message.info(self._win_id, "{} {} yanked to {}".format(
len(s), "char" if len(s) == 1 else "chars", target))
if not keep:
2015-07-16 13:57:53 +02:00
modeman.maybe_leave(self._win_id, KeyMode.caret, "yank selected")
2015-04-28 16:50:42 +02:00
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
def toggle_selection(self):
"""Toggle caret selection mode."""
2015-05-11 20:32:27 +02:00
widget = self._current_widget()
widget.selection_enabled = not widget.selection_enabled
mainwindow = objreg.get('main-window', scope='window',
window=self._win_id)
2015-05-13 21:52:42 +02:00
mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
def drop_selection(self):
"""Drop selection and keep selection mode enabled."""
self._current_widget().triggerPageAction(QWebPage.MoveToNextChar)
2015-04-22 07:13:56 +02:00
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
@cmdutils.argument('count', count=True)
2015-04-22 07:13:56 +02:00
def debug_webaction(self, action, count=1):
"""Execute a webaction.
See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the
available actions.
Args:
action: The action to execute, e.g. MoveToNextChar.
count: How many times to repeat the action.
"""
member = getattr(QWebPage, action, None)
if not isinstance(member, QWebPage.WebAction):
raise cmdexc.CommandError("{} is not a valid web action!".format(
2015-04-22 07:46:01 +02:00
action))
2015-04-22 07:13:56 +02:00
view = self._current_widget()
for _ in range(count):
view.triggerPageAction(member)
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0, no_cmd_split=True)
2015-06-12 11:24:04 +02:00
def jseval(self, js_code, quiet=False):
"""Evaluate a JavaScript string.
Args:
js_code: The string to evaluate.
2015-06-12 11:24:04 +02:00
quiet: Don't show resulting JS object.
"""
2015-06-12 11:22:37 +02:00
frame = self._current_widget().page().mainFrame()
out = frame.evaluateJavaScript(js_code)
2015-06-12 11:24:04 +02:00
if quiet:
return
if out is None:
# Getting the actual error (if any) seems to be difficult. The
# error does end up in BrowserPage.javaScriptConsoleMessage(), but
# distinguishing between :jseval errors and errors from the webpage
# is not trivial...
message.info(self._win_id, 'No output or error')
else:
# The output can be a string, number, dict, array, etc. But *don't*
# output too much data, as this will make qutebrowser hang
out = str(out)
if len(out) > 5000:
message.info(self._win_id, out[:5000] + ' [...trimmed...]')
else:
message.info(self._win_id, out)
@cmdutils.register(instance='command-dispatcher', scope='window')
def fake_key(self, keystring, global_=False):
"""Send a fake keypress or key string to the website or qutebrowser.
:fake-key xy - sends the keychain 'xy'
:fake-key <Ctrl-x> - sends Ctrl-x
:fake-key <Escape> - sends the escape key
Args:
keystring: The keystring to send.
global_: If given, the keys are sent to the qutebrowser UI.
"""
try:
keyinfos = utils.parse_keystring(keystring)
except utils.KeyParseError as e:
raise cmdexc.CommandError(str(e))
for keyinfo in keyinfos:
press_event = QKeyEvent(QEvent.KeyPress, keyinfo.key,
keyinfo.modifiers, keyinfo.text)
release_event = QKeyEvent(QEvent.KeyRelease, keyinfo.key,
keyinfo.modifiers, keyinfo.text)
if global_:
receiver = QApplication.focusWindow()
if receiver is None:
raise cmdexc.CommandError("No focused window!")
else:
try:
receiver = objreg.get('webview', scope='tab',
tab='current')
2015-11-24 16:28:56 +01:00
except objreg.RegistryUnavailableError:
raise cmdexc.CommandError("No focused webview!")
QApplication.postEvent(receiver, press_event)
QApplication.postEvent(receiver, release_event)
2016-01-13 21:05:48 +01:00
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
def debug_clear_ssl_errors(self):
"""Clear remembered SSL error answers."""
nam = self._current_widget().page().networkAccessManager()
nam.clear_all_ssl_errors()
@cmdutils.register(instance='command-dispatcher', scope='window')
def edit_url(self, url=None, bg=False, tab=False, window=False):
"""Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the
`general -> editor` config option.
Args:
url: URL to edit; defaults to the current page url.
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
"""
cmdutils.check_exclusive((tab, bg, window), 'tbw')
old_url = self._current_url().toString()
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
# Passthrough for openurl args (e.g. -t, -b, -w)
2016-04-05 18:37:50 +02:00
ed.editing_finished.connect(functools.partial(
self._open_if_changed, old_url=old_url, bg=bg, tab=tab,
window=window))
ed.edit(url or old_url)
@cmdutils.register(instance='command-dispatcher', scope='window')
def set_mark(self, key):
2016-04-13 04:00:29 +02:00
"""Set a mark at the current scroll position in the current tab.
Args:
key: mark identifier; capital indicates a global mark
"""
self._tabbed_browser.set_mark(key)
@cmdutils.register(instance='command-dispatcher', scope='window')
def jump_mark(self, key):
2016-04-13 04:00:29 +02:00
"""Jump to the mark named by `key`.
Args:
key: mark identifier; capital indicates a global mark
"""
self._tabbed_browser.jump_mark(key)
def _open_if_changed(self, url=None, old_url=None, bg=False, tab=False,
window=False):
"""Open a URL unless it's already open in the tab.
Args:
old_url: The original URL to compare against.
url: The URL to open.
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
"""
if bg or tab or window or url != old_url:
self.openurl(url=url, bg=bg, tab=tab, window=window)