Add components.misccommands

This commit is contained in:
Florian Bruhin 2018-11-30 15:56:16 +01:00
parent 7788a91ed2
commit 979be017c3
4 changed files with 314 additions and 290 deletions

View File

@ -77,7 +77,8 @@ from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
usertypes, standarddir, error, qtutils) usertypes, standarddir, error, qtutils)
# pylint: disable=unused-import # pylint: disable=unused-import
# We import those to run the cmdutils.register decorators. # We import those to run the cmdutils.register decorators.
from qutebrowser.components import scrollcommands, caretcommands, zoomcommands from qutebrowser.components import (scrollcommands, caretcommands,
zoomcommands, misccommands)
from qutebrowser.mainwindow.statusbar import command from qutebrowser.mainwindow.statusbar import command
from qutebrowser.misc import utilcmds from qutebrowser.misc import utilcmds
# pylint: enable=unused-import # pylint: enable=unused-import

View File

@ -19,7 +19,6 @@
"""Command dispatcher for TabbedBrowser.""" """Command dispatcher for TabbedBrowser."""
import os
import os.path import os.path
import shlex import shlex
import functools import functools
@ -27,7 +26,6 @@ import typing
from PyQt5.QtWidgets import QApplication, QTabBar from PyQt5.QtWidgets import QApplication, QTabBar
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
from PyQt5.QtPrintSupport import QPrintPreviewDialog
from qutebrowser.commands import userscripts, runners from qutebrowser.commands import userscripts, runners
from qutebrowser.api import cmdutils from qutebrowser.api import cmdutils
@ -370,83 +368,6 @@ class CommandDispatcher:
if parsed is not None: if parsed is not None:
yield parsed yield parsed
@cmdutils.register(instance='command-dispatcher', name='reload',
scope='window')
@cmdutils.argument('count', value=cmdutils.Value.count)
def reloadpage(self, force=False, count=None):
"""Reload the current/[count]th tab.
Args:
count: The tab index to reload, or None.
force: Bypass the page cache.
"""
tab = self._cntwidget(count)
if tab is not None:
tab.reload(force=force)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', value=cmdutils.Value.count)
def stop(self, count=None):
"""Stop loading in the current/[count]th tab.
Args:
count: The tab index to stop, or None.
"""
tab = self._cntwidget(count)
if tab is not None:
tab.stop()
def _print_preview(self, tab):
"""Show a print preview."""
def print_callback(ok):
if not ok:
message.error("Printing failed!")
tab.printing.check_preview_support()
diag = QPrintPreviewDialog(tab)
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.setWindowFlags(diag.windowFlags() | Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
diag.paintRequested.connect(functools.partial(
tab.printing.to_printer, callback=print_callback))
diag.exec_()
def _print_pdf(self, tab, filename):
"""Print to the given PDF file."""
tab.printing.check_pdf_support()
filename = os.path.expanduser(filename)
directory = os.path.dirname(filename)
if directory and not os.path.exists(directory):
os.mkdir(directory)
tab.printing.to_pdf(filename)
log.misc.debug("Print to file: {}".format(filename))
@cmdutils.register(instance='command-dispatcher', name='print',
scope='window')
@cmdutils.argument('count', value=cmdutils.Value.count)
@cmdutils.argument('pdf', flag='f', metavar='file')
def printpage(self, preview=False, count=None, *, pdf=None):
"""Print the current/[count]th tab.
Args:
preview: Show preview instead of printing.
count: The tab index to print, or None.
pdf: The file path to write the PDF to.
"""
tab = self._cntwidget(count)
if tab is None:
return
try:
if preview:
self._print_preview(tab)
elif pdf:
self._print_pdf(tab, pdf)
else:
tab.printing.show_dialog()
except browsertab.WebTabError as e:
raise cmdutils.CommandError(e)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def tab_clone(self, bg=False, window=False): def tab_clone(self, bg=False, window=False):
"""Duplicate the current tab. """Duplicate the current tab.
@ -1110,11 +1031,6 @@ class CommandDispatcher:
proc.start(cmd, args) proc.start(cmd, args)
proc.finished.connect(_on_proc_finished) proc.finished.connect(_on_proc_finished)
@cmdutils.register(instance='command-dispatcher', scope='window')
def home(self):
"""Open main startpage in current tab."""
self.openurl(config.val.url.start_pages[0])
def _run_userscript(self, selection, cmd, args, verbose, count): def _run_userscript(self, selection, cmd, args, verbose, count):
"""Run a userscript given as argument. """Run a userscript given as argument.
@ -1384,30 +1300,6 @@ class CommandDispatcher:
else: else:
tab.action.show_source(pygments) tab.action.show_source(pygments)
@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.
"""
tab = self._current_widget()
dest = os.path.expanduser(dest)
def callback(data):
"""Write the data to disk."""
try:
with open(dest, 'w', encoding='utf-8') as f:
f.write(data)
except OSError as e:
message.error('Could not write page: {}'.format(e))
else:
message.info("Dumped page to {}.".format(dest))
tab.dump_async(callback, plain=plain)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def history(self, tab=True, bg=False, window=False): def history(self, tab=True, bg=False, window=False):
"""Show browsing history. """Show browsing history.
@ -1525,75 +1417,6 @@ class CommandDispatcher:
message.error(str(e)) message.error(str(e))
ed.backup() ed.backup()
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
scope='window')
def insert_text(self, text):
"""Insert text at cursor position.
Args:
text: The text to insert.
"""
tab = self._current_widget()
def _insert_text_cb(elem):
if elem is None:
message.error("No element focused!")
return
try:
elem.insert_text(text)
except webelem.Error as e:
message.error(str(e))
return
tab.elements.find_focused(_insert_text_cb)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('filter_', choices=['id'])
def click_element(self, filter_: str, value: str, *,
target: usertypes.ClickTarget =
usertypes.ClickTarget.normal,
force_event: bool = False) -> None:
"""Click the element matching the given filter.
The given filter needs to result in exactly one element, otherwise, an
error is shown.
Args:
filter_: How to filter the elements.
id: Get an element based on its ID.
value: The value to filter for.
target: How to open the clicked element (normal/tab/tab-bg/window).
force_event: Force generating a fake click event.
"""
tab = self._current_widget()
def single_cb(elem):
"""Click a single element."""
if elem is None:
message.error("No element found with id {}!".format(value))
return
try:
elem.click(target, force_event=force_event)
except webelem.Error as e:
message.error(str(e))
return
# def multiple_cb(elems):
# """Click multiple elements (with only one expected)."""
# if not elems:
# message.error("No element found!")
# return
# elif len(elems) != 1:
# message.error("{} elements found!".format(len(elems)))
# return
# elems[0].click(target)
handlers = {
'id': (tab.elements.find_id, single_cb),
}
handler, callback = handlers[filter_]
handler(value, callback)
def _search_cb(self, found, *, tab, old_scroll_pos, options, text, prev): def _search_cb(self, found, *, tab, old_scroll_pos, options, text, prev):
"""Callback called from search/search_next/search_prev. """Callback called from search/search_next/search_prev.
@ -1721,27 +1544,6 @@ class CommandDispatcher:
tab.search.prev_result() tab.search.prev_result()
tab.search.prev_result(result_cb=cb) tab.search.prev_result(result_cb=cb)
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
@cmdutils.argument('count', value=cmdutils.Value.count)
def debug_webaction(self, action, count=1):
"""Execute a webaction.
Available actions:
http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit)
http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
Args:
action: The action to execute, e.g. MoveToNextChar.
count: How many times to repeat the action.
"""
tab = self._current_widget()
for _ in range(count):
try:
tab.action.run_string(action)
except browsertab.WebTabError as e:
raise cmdutils.CommandError(str(e))
@cmdutils.register(instance='command-dispatcher', scope='window', @cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0, no_cmd_split=True) maxsplit=0, no_cmd_split=True)
def jseval(self, js_code: str, file: bool = False, quiet: bool = False, *, def jseval(self, js_code: str, file: bool = False, quiet: bool = False, *,
@ -1920,20 +1722,3 @@ class CommandDispatcher:
window = self._tabbed_browser.widget.window() window = self._tabbed_browser.widget.window()
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen) window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
@cmdutils.register(instance='command-dispatcher', scope='window',
name='tab-mute')
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_mute(self, count=None):
"""Mute/Unmute the current/[count]th tab.
Args:
count: The tab index to mute or unmute, or None
"""
tab = self._cntwidget(count)
if tab is None:
return
try:
tab.audio.set_muted(tab.audio.is_muted(), override=True)
except browsertab.WebTabError as e:
raise cmdutils.CommandError(e)

View File

@ -0,0 +1,312 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Various commands."""
import os
import signal
import functools
import typing
import logging
try:
import hunter
except ImportError:
hunter = None
from qutebrowser.api import cmdutils, apitypes, message, config
from PyQt5.QtCore import Qt
from PyQt5.QtPrintSupport import QPrintPreviewDialog
@cmdutils.register(name='reload')
@cmdutils.argument('count', value=cmdutils.Value.count)
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
def reloadpage(tab, force=False, count=None):
"""Reload the current/[count]th tab.
Args:
count: The tab index to reload, or None.
force: Bypass the page cache.
"""
if tab is not None:
tab.reload(force=force)
@cmdutils.register()
@cmdutils.argument('count', value=cmdutils.Value.count)
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
def stop(tab, count=None):
"""Stop loading in the current/[count]th tab.
Args:
count: The tab index to stop, or None.
"""
if tab is not None:
tab.stop()
def _print_preview(tab):
"""Show a print preview."""
def print_callback(ok):
if not ok:
message.error("Printing failed!")
tab.printing.check_preview_support()
diag = QPrintPreviewDialog(tab)
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.setWindowFlags(diag.windowFlags() | Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
diag.paintRequested.connect(functools.partial(
tab.printing.to_printer, callback=print_callback))
diag.exec_()
def _print_pdf(tab, filename):
"""Print to the given PDF file."""
tab.printing.check_pdf_support()
filename = os.path.expanduser(filename)
directory = os.path.dirname(filename)
if directory and not os.path.exists(directory):
os.mkdir(directory)
tab.printing.to_pdf(filename)
logging.getLogger('misc').debug("Print to file: {}".format(filename))
@cmdutils.register(name='print')
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
@cmdutils.argument('count', value=cmdutils.Value.count)
@cmdutils.argument('pdf', flag='f', metavar='file')
def printpage(tab, preview=False, count=None, *, pdf=None):
"""Print the current/[count]th tab.
Args:
preview: Show preview instead of printing.
count: The tab index to print, or None.
pdf: The file path to write the PDF to.
"""
if tab is None:
return
try:
if preview:
_print_preview(tab)
elif pdf:
_print_pdf(tab, pdf)
else:
tab.printing.show_dialog()
except apitypes.WebTabError as e:
raise cmdutils.CommandError(e)
@cmdutils.register()
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
def home(tab):
"""Open main startpage in current tab."""
tab.load_url(config.val.url.start_pages[0])
@cmdutils.register(debug=True)
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
def debug_dump_page(tab, 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.
"""
dest = os.path.expanduser(dest)
def callback(data):
"""Write the data to disk."""
try:
with open(dest, 'w', encoding='utf-8') as f:
f.write(data)
except OSError as e:
message.error('Could not write page: {}'.format(e))
else:
message.info("Dumped page to {}.".format(dest))
tab.dump_async(callback, plain=plain)
@cmdutils.register(maxsplit=0)
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
def insert_text(tab, text):
"""Insert text at cursor position.
Args:
text: The text to insert.
"""
def _insert_text_cb(elem):
if elem is None:
message.error("No element focused!")
return
try:
elem.insert_text(text)
except apitypes.WebElemError as e:
message.error(str(e))
return
tab.elements.find_focused(_insert_text_cb)
@cmdutils.register()
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
@cmdutils.argument('filter_', choices=['id'])
def click_element(tab, filter_: str, value: str, *,
target: apitypes.ClickTarget =
apitypes.ClickTarget.normal,
force_event: bool = False) -> None:
"""Click the element matching the given filter.
The given filter needs to result in exactly one element, otherwise, an
error is shown.
Args:
filter_: How to filter the elements.
id: Get an element based on its ID.
value: The value to filter for.
target: How to open the clicked element (normal/tab/tab-bg/window).
force_event: Force generating a fake click event.
"""
def single_cb(elem):
"""Click a single element."""
if elem is None:
message.error("No element found with id {}!".format(value))
return
try:
elem.click(target, force_event=force_event)
except apitypes.WebElemError as e:
message.error(str(e))
return
handlers = {
'id': (tab.elements.find_id, single_cb),
}
handler, callback = handlers[filter_]
handler(value, callback)
@cmdutils.register(debug=True)
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
@cmdutils.argument('count', value=cmdutils.Value.count)
def debug_webaction(tab, action, count=1):
"""Execute a webaction.
Available actions:
http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit)
http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
Args:
action: The action to execute, e.g. MoveToNextChar.
count: How many times to repeat the action.
"""
for _ in range(count):
try:
tab.action.run_string(action)
except apitypes.WebTabError as e:
raise cmdutils.CommandError(str(e))
@cmdutils.register()
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_mute(tab, count=None):
"""Mute/Unmute the current/[count]th tab.
Args:
count: The tab index to mute or unmute, or None
"""
if tab is None:
return
try:
tab.audio.set_muted(tab.audio.is_muted(), override=True)
except apitypes.WebTabError as e:
raise cmdutils.CommandError(e)
@cmdutils.register()
def nop():
"""Do nothing."""
@cmdutils.register()
def message_error(text):
"""Show an error message in the statusbar.
Args:
text: The text to show.
"""
message.error(text)
@cmdutils.register()
@cmdutils.argument('count', value=cmdutils.Value.count)
def message_info(text, count=1):
"""Show an info message in the statusbar.
Args:
text: The text to show.
count: How many times to show the message
"""
for _ in range(count):
message.info(text)
@cmdutils.register()
def message_warning(text):
"""Show a warning message in the statusbar.
Args:
text: The text to show.
"""
message.warning(text)
@cmdutils.register(debug=True)
@cmdutils.argument('typ', choices=['exception', 'segfault'])
def debug_crash(typ='exception'):
"""Crash for debugging purposes.
Args:
typ: either 'exception' or 'segfault'.
"""
if typ == 'segfault':
os.kill(os.getpid(), signal.SIGSEGV)
raise Exception("Segfault failed (wat.)")
else:
raise Exception("Forced crash")
@cmdutils.register(debug=True, maxsplit=0, no_cmd_split=True)
def debug_trace(expr=""):
"""Trace executed code via hunter.
Args:
expr: What to trace, passed to hunter.
"""
if hunter is None:
raise cmdutils.CommandError("You need to install 'hunter' to use this "
"command!")
try:
eval('hunter.trace({})'.format(expr))
except Exception as e:
raise cmdutils.CommandError("{}: {}".format(e.__class__.__name__, e))

View File

@ -24,11 +24,6 @@ import os
import signal import signal
import traceback import traceback
try:
import hunter
except ImportError:
hunter = None
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
# so it's available for :debug-pyeval # so it's available for :debug-pyeval
from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
@ -111,60 +106,12 @@ def run_with_count(count_arg: int, command: str, win_id: int,
runners.CommandRunner(win_id).run(command, count_arg * count) runners.CommandRunner(win_id).run(command, count_arg * count)
@cmdutils.register()
def message_error(text):
"""Show an error message in the statusbar.
Args:
text: The text to show.
"""
message.error(text)
@cmdutils.register()
@cmdutils.argument('count', value=cmdutils.Value.count)
def message_info(text, count=1):
"""Show an info message in the statusbar.
Args:
text: The text to show.
count: How many times to show the message
"""
for _ in range(count):
message.info(text)
@cmdutils.register()
def message_warning(text):
"""Show a warning message in the statusbar.
Args:
text: The text to show.
"""
message.warning(text)
@cmdutils.register() @cmdutils.register()
def clear_messages(): def clear_messages():
"""Clear all message notifications.""" """Clear all message notifications."""
message.global_bridge.clear_messages.emit() message.global_bridge.clear_messages.emit()
@cmdutils.register(debug=True)
@cmdutils.argument('typ', choices=['exception', 'segfault'])
def debug_crash(typ='exception'):
"""Crash for debugging purposes.
Args:
typ: either 'exception' or 'segfault'.
"""
if typ == 'segfault':
os.kill(os.getpid(), signal.SIGSEGV)
raise Exception("Segfault failed (wat.)")
else:
raise Exception("Forced crash")
@cmdutils.register(debug=True) @cmdutils.register(debug=True)
def debug_all_objects(): def debug_all_objects():
"""Print a list of all objects to the debug log.""" """Print a list of all objects to the debug log."""
@ -220,22 +167,6 @@ def debug_console():
con_widget.show() con_widget.show()
@cmdutils.register(debug=True, maxsplit=0, no_cmd_split=True)
def debug_trace(expr=""):
"""Trace executed code via hunter.
Args:
expr: What to trace, passed to hunter.
"""
if hunter is None:
raise cmdutils.CommandError("You need to install 'hunter' to use this "
"command!")
try:
eval('hunter.trace({})'.format(expr))
except Exception as e:
raise cmdutils.CommandError("{}: {}".format(e.__class__.__name__, e))
@cmdutils.register(maxsplit=0, debug=True, no_cmd_split=True) @cmdutils.register(maxsplit=0, debug=True, no_cmd_split=True)
def debug_pyeval(s, file=False, quiet=False): def debug_pyeval(s, file=False, quiet=False):
"""Evaluate a python string and display the results as a web page. """Evaluate a python string and display the results as a web page.
@ -371,11 +302,6 @@ def window_only(current_win_id):
window.close() window.close()
@cmdutils.register()
def nop():
"""Do nothing."""
@cmdutils.register() @cmdutils.register()
@cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('win_id', value=cmdutils.Value.win_id)
def version(win_id, paste=False): def version(win_id, paste=False):