Start initial newcmd stuff.

This commit is contained in:
Florian Bruhin 2014-09-02 21:54:07 +02:00
parent 84a034d7e9
commit a811f8cb07
12 changed files with 380 additions and 360 deletions

View File

@ -581,7 +581,7 @@ class Application(QApplication):
self._destroy_crashlogfile() self._destroy_crashlogfile()
sys.exit(1) sys.exit(1)
@cmdutils.register(instance='', nargs=0) @cmdutils.register(instance='', ignore_args=True)
def restart(self, shutdown=True, pages=None): def restart(self, shutdown=True, pages=None):
"""Restart qutebrowser while keeping existing tabs open.""" """Restart qutebrowser while keeping existing tabs open."""
# We don't use _recover_pages here as it's too forgiving when # We don't use _recover_pages here as it's too forgiving when

View File

@ -78,9 +78,7 @@ class CommandDispatcher:
if perc is None and count is None: if perc is None and count is None:
perc = 100 perc = 100
elif perc is None: elif perc is None:
perc = int(count) perc = count
else:
perc = float(perc)
perc = qtutils.check_overflow(perc, 'int', fatal=False) perc = qtutils.check_overflow(perc, 'int', fatal=False)
frame = self._current_widget().page().currentFrame() frame = self._current_widget().page().currentFrame()
m = frame.scrollBarMaximum(orientation) m = frame.scrollBarMaximum(orientation)
@ -161,19 +159,26 @@ class CommandDispatcher:
@cmdutils.register(instance='mainwindow.tabs.cmd', name='open', @cmdutils.register(instance='mainwindow.tabs.cmd', name='open',
split=False) split=False)
def openurl(self, urlstr, count=None): def openurl(self, urlstr, bg=False, tab=False, count=None):
"""Open a URL in the current/[count]th tab. """Open a URL in the current/[count]th tab.
Args: Args:
urlstr: The URL to open, as string. urlstr: The URL to open, as string.
bg: Whether to open in a background tab.
tab: Whether to open in a tab.
count: The tab index to open the URL in, or None. count: The tab index to open the URL in, or None.
""" """
tab = self._tabs.cntwidget(count)
try: try:
url = urlutils.fuzzy_url(urlstr) url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError as e: except urlutils.FuzzyUrlError as e:
raise cmdexc.CommandError(e) raise cmdexc.CommandError(e)
if tab is None: if tab:
self._tabs.tabopen(url, background=False, explicit=True)
elif bg:
self._tabs.tabopen(url, background=True, explicit=True)
else:
curtab = self._tabs.cntwidget(count)
if curtab is None:
if count is None: if count is None:
# We want to open a URL in the current tab, but none exists # We want to open a URL in the current tab, but none exists
# yet. # yet.
@ -182,7 +187,7 @@ class CommandDispatcher:
# Explicit count with a tab that doesn't exist. # Explicit count with a tab that doesn't exist.
return return
else: else:
tab.openurl(url) curtab.openurl(url)
@cmdutils.register(instance='mainwindow.tabs.cmd', name='reload') @cmdutils.register(instance='mainwindow.tabs.cmd', name='reload')
def reloadpage(self, count=None): def reloadpage(self, count=None):
@ -206,29 +211,12 @@ class CommandDispatcher:
if tab is not None: if tab is not None:
tab.stop() tab.stop()
@cmdutils.register(instance='mainwindow.tabs.cmd')
def print_preview(self, count=None):
"""Preview printing of the current/[count]th tab.
Args:
count: The tab index to print, or None.
"""
if not qtutils.check_print_compat():
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
raise cmdexc.CommandError(
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
tab = self._tabs.cntwidget(count)
if tab is not None:
preview = QPrintPreviewDialog()
preview.setAttribute(Qt.WA_DeleteOnClose)
preview.paintRequested.connect(tab.print)
preview.exec_()
@cmdutils.register(instance='mainwindow.tabs.cmd', name='print') @cmdutils.register(instance='mainwindow.tabs.cmd', name='print')
def printpage(self, count=None): def printpage(self, preview=False, count=None):
"""Print the current/[count]th tab. """Print the current/[count]th tab.
Args: Args:
preview: Whether to preview instead of printing.
count: The tab index to print, or None. count: The tab index to print, or None.
""" """
if not qtutils.check_print_compat(): if not qtutils.check_print_compat():
@ -237,9 +225,15 @@ class CommandDispatcher:
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!") "Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
tab = self._tabs.cntwidget(count) tab = self._tabs.cntwidget(count)
if tab is not None: if tab is not None:
printdiag = QPrintDialog() if preview:
printdiag.setAttribute(Qt.WA_DeleteOnClose) diag = QPrintPreviewDialog()
printdiag.open(lambda: tab.print(printdiag.printer())) diag.setAttribute(Qt.WA_DeleteOnClose)
diag.paintRequested.connect(tab.print)
diag.exec_()
else:
diag = QPrintDialog()
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.open(lambda: tab.print(printdiag.printer()))
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def back(self, count=1): def back(self, count=1):
@ -262,7 +256,8 @@ class CommandDispatcher:
self._current_widget().go_forward() self._current_widget().go_forward()
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def hint(self, group='all', target='normal', *args): def hint(self, group=webelem.Group.all, target=hints.Target.normal,
*args : {'nargs': '*'}):
"""Start hinting. """Start hinting.
Args: Args:
@ -281,9 +276,9 @@ class CommandDispatcher:
- `yank-primary`: Yank the link to the primary selection. - `yank-primary`: Yank the link to the primary selection.
- `fill`: Fill the commandline with the command given as - `fill`: Fill the commandline with the command given as
argument. argument.
- `cmd-tab`: Fill the commandline with `:open-tab` and the - `cmd-tab`: Fill the commandline with `:open -t` and the
link. link.
- `cmd-tag-bg`: Fill the commandline with `:open-tab-bg` and - `cmd-tag-bg`: Fill the commandline with `:open -b` and
the link. the link.
- `rapid`: Open the link in a new tab and stay in hinting mode. - `rapid`: Open the link in a new tab and stay in hinting mode.
- `download`: Download the link. - `download`: Download the link.
@ -305,18 +300,8 @@ class CommandDispatcher:
frame = widget.page().mainFrame() frame = widget.page().mainFrame()
if frame is None: if frame is None:
raise cmdexc.CommandError("No frame focused!") raise cmdexc.CommandError("No frame focused!")
try: widget.hintmanager.start(frame, self._tabs.current_url(), group,
group_enum = webelem.Group[group.replace('-', '_')] target, *args)
except KeyError:
raise cmdexc.CommandError("Unknown hinting group {}!".format(
group))
try:
target_enum = hints.Target[target.replace('-', '_')]
except KeyError:
raise cmdexc.CommandError("Unknown hinting target {}!".format(
target))
widget.hintmanager.start(frame, self._tabs.current_url(), group_enum,
target_enum, *args)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True) @cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def follow_hint(self): def follow_hint(self):
@ -324,43 +309,31 @@ class CommandDispatcher:
self._current_widget().hintmanager.follow_hint() self._current_widget().hintmanager.follow_hint()
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def prev_page(self): def prev_page(self, tab=False):
"""Open a "previous" link. """Open a "previous" link.
This tries to automaticall click on typical "Previous Page" links using This tries to automaticall click on typical "Previous Page" links using
some heuristics. some heuristics.
Args:
tab: Whether to open a new tab.
""" """
self._prevnext(prev=True, newtab=False) self._prevnext(prev=True, newtab=tab)
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def next_page(self): def next_page(self, tab=False):
"""Open a "next" link. """Open a "next" link.
This tries to automatically click on typical "Next Page" links using This tries to automatically click on typical "Next Page" links using
some heuristics. some heuristics.
Args:
tab: Whether to open a new tab.
""" """
self._prevnext(prev=False, newtab=False) self._prevnext(prev=False, newtab=tab)
@cmdutils.register(instance='mainwindow.tabs.cmd')
def prev_page_tab(self):
"""Open a "previous" link in a new tab.
This tries to automatically click on typical "Previous Page" links
using some heuristics.
"""
self._prevnext(prev=True, newtab=True)
@cmdutils.register(instance='mainwindow.tabs.cmd')
def next_page_tab(self):
"""Open a "next" link in a new tab.
This tries to automatically click on typical "Previous Page" links
using some heuristics.
"""
self._prevnext(prev=False, newtab=True)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True) @cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def scroll(self, dx, dy, count=1): def scroll(self, dx : float, dy : float, count=1):
"""Scroll the current tab by 'count * dx/dy'. """Scroll the current tab by 'count * dx/dy'.
Args: Args:
@ -368,40 +341,30 @@ class CommandDispatcher:
dy: How much to scroll in x-direction. dy: How much to scroll in x-direction.
count: multiplier count: multiplier
""" """
dx = int(int(count) * float(dx)) dx *= count
dy = int(int(count) * float(dy)) dy *= count
cmdutils.check_overflow(dx, 'int') cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int') cmdutils.check_overflow(dy, 'int')
self._current_widget().page().currentFrame().scroll(dx, dy) self._current_widget().page().currentFrame().scroll(dx, dy)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True) @cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def scroll_perc_x(self, perc=None, count=None): def scroll_perc(self, perc : float = None,
"""Scroll horizontally to a specific percentage of the page. horizontal : {'flag': 'x'} = False, count=None):
"""Scroll to a specific percentage of the page.
The percentage can be given either as argument or as count. The percentage can be given either as argument or as count.
If no percentage is given, the page is scrolled to the end. If no percentage is given, the page is scrolled to the end.
Args: Args:
perc: Percentage to scroll. perc: Percentage to scroll.
horizontal: Whether to scroll horizontally.
count: Percentage to scroll. count: Percentage to scroll.
""" """
self._scroll_percent(perc, count, Qt.Horizontal) self._scroll_percent(perc, count,
Qt.Horizontal if horizontal else Qt.Vertical)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True) @cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def scroll_perc_y(self, perc=None, count=None): def scroll_page(self, x : int, y : int, count=1):
"""Scroll vertically to a specific percentage of the page.
The percentage can be given either as argument or as count.
If no percentage is given, the page is scrolled to the end.
Args:
perc: Percentage to scroll.
count: Percentage to scroll.
"""
self._scroll_percent(perc, count, Qt.Vertical)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def scroll_page(self, x, y, count=1):
"""Scroll the frame page-wise. """Scroll the frame page-wise.
Args: Args:
@ -411,21 +374,25 @@ class CommandDispatcher:
""" """
frame = self._current_widget().page().currentFrame() frame = self._current_widget().page().currentFrame()
size = frame.geometry() size = frame.geometry()
dx = int(count) * float(x) * size.width() dx = count * x * size.width()
dy = int(count) * float(y) * size.height() dy = count * y * size.height()
cmdutils.check_overflow(dx, 'int') cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int') cmdutils.check_overflow(dy, 'int')
frame.scroll(dx, dy) frame.scroll(dx, dy)
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def yank(self, sel=False): def yank(self, title=False, sel=False):
"""Yank the current URL to the clipboard or primary selection. """Yank the current URL/title to the clipboard or primary selection.
Args: Args:
sel: True to use primary selection, False to use clipboard sel: True to use primary selection, False to use clipboard
title: Whether to yank the title instead of the URL.
""" """
clipboard = QApplication.clipboard() clipboard = QApplication.clipboard()
urlstr = self._tabs.current_url().toString( if title:
s = self._tabs.tabText(self._tabs.currentIndex())
else:
s = self._tabs.current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword) QUrl.FullyEncoded | QUrl.RemovePassword)
if sel and clipboard.supportsSelection(): if sel and clipboard.supportsSelection():
mode = QClipboard.Selection mode = QClipboard.Selection
@ -433,30 +400,10 @@ class CommandDispatcher:
else: else:
mode = QClipboard.Clipboard mode = QClipboard.Clipboard
target = "clipboard" target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, urlstr)) log.misc.debug("Yanking to {}: '{}'".format(target, s))
clipboard.setText(urlstr, mode) clipboard.setText(s, mode)
message.info("URL yanked to {}".format(target)) message.info("URL yanked to {}".format(target))
@cmdutils.register(instance='mainwindow.tabs.cmd')
def yank_title(self, sel=False):
"""Yank the current title to the clipboard or primary selection.
Args:
sel: True to use primary selection, False to use clipboard
"""
clipboard = QApplication.clipboard()
title = self._tabs.tabText(self._tabs.currentIndex())
mode = QClipboard.Selection if sel else QClipboard.Clipboard
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
target = "primary selection"
else:
mode = QClipboard.Clipboard
target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, title))
clipboard.setText(title, mode)
message.info("Title yanked to {}".format(target))
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def zoom_in(self, count=1): def zoom_in(self, count=1):
"""Increase the zoom level for the current tab. """Increase the zoom level for the current tab.
@ -503,24 +450,6 @@ class CommandDispatcher:
continue continue
self._tabs.close_tab(tab) self._tabs.close_tab(tab)
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
def open_tab(self, urlstr):
"""Open a new tab with a given url."""
try:
url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError as e:
raise cmdexc.CommandError(e)
self._tabs.tabopen(url, background=False, explicit=True)
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
def open_tab_bg(self, urlstr):
"""Open a new tab in background."""
try:
url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError as e:
raise cmdexc.CommandError(e)
self._tabs.tabopen(url, background=True, explicit=True)
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def undo(self): def undo(self):
"""Re-open a closed tab (optionally skipping [count] closed tabs).""" """Re-open a closed tab (optionally skipping [count] closed tabs)."""
@ -559,7 +488,7 @@ class CommandDispatcher:
else: else:
raise cmdexc.CommandError("Last tab") raise cmdexc.CommandError("Last tab")
@cmdutils.register(instance='mainwindow.tabs.cmd', nargs=(0, 1)) @cmdutils.register(instance='mainwindow.tabs.cmd')
def paste(self, sel=False, tab=False): def paste(self, sel=False, tab=False):
"""Open a page from the clipboard. """Open a page from the clipboard.
@ -589,16 +518,7 @@ class CommandDispatcher:
widget.openurl(url) widget.openurl(url)
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def paste_tab(self, sel=False): def tab_focus(self, index : int = None, count=None):
"""Open a page from the clipboard in a new tab.
Args:
sel: True to use primary selection, False to use clipboard
"""
self.paste(sel, True)
@cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_focus(self, index=None, count=None):
"""Select the tab given as argument/[count]. """Select the tab given as argument/[count].
Args: Args:
@ -622,7 +542,7 @@ class CommandDispatcher:
idx)) idx))
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_move(self, direction=None, count=None): def tab_move(self, direction : ('+', '-') = None, count=None):
"""Move the current tab. """Move the current tab.
Args: Args:
@ -698,26 +618,19 @@ class CommandDispatcher:
quickmarks.prompt_save(self._tabs.current_url()) quickmarks.prompt_save(self._tabs.current_url())
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def quickmark_load(self, name): def quickmark_load(self, name, tab=False, bg=False):
"""Load a quickmark.""" """Load a quickmark."""
urlstr = quickmarks.get(name) urlstr = quickmarks.get(name)
url = QUrl(urlstr) url = QUrl(urlstr)
if not url.isValid(): if not url.isValid():
raise cmdexc.CommandError("Invalid URL {} ({})".format( raise cmdexc.CommandError("Invalid URL {} ({})".format(
urlstr, url.errorString())) urlstr, url.errorString()))
self._current_widget().openurl(url) if tab:
@cmdutils.register(instance='mainwindow.tabs.cmd')
def quickmark_load_tab(self, name):
"""Load a quickmark in a new tab."""
url = quickmarks.get(name)
self._tabs.tabopen(url, background=False, explicit=True) self._tabs.tabopen(url, background=False, explicit=True)
elif bg:
@cmdutils.register(instance='mainwindow.tabs.cmd')
def quickmark_load_tab_bg(self, name):
"""Load a quickmark in a new background tab."""
url = quickmarks.get(name)
self._tabs.tabopen(url, background=True, explicit=True) self._tabs.tabopen(url, background=True, explicit=True)
else:
self._current_widget().openurl(url)
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector') @cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
def toggle_inspector(self): def toggle_inspector(self):

View File

@ -0,0 +1,40 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 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/>.
"""argparse.ArgumentParser subclass to parse qutebrowser commands."""
import argparse
class ArgumentParserError(Exception):
"""Exception raised when the ArgumentParser signals an error."""
class ArgumentParser(argparse.ArgumentParser):
def __init__(self):
super().__init__(add_help=False)
def exit(self, status=0, msg=None):
raise AssertionError('exit called, this should never happen. '
'Status: {}, message: {}'.format(status, msg))
def error(self, msg):
raise ArgumentParserError(msg)

View File

@ -23,11 +23,12 @@ Module attributes:
cmd_dict: A mapping from command-strings to command objects. cmd_dict: A mapping from command-strings to command objects.
""" """
import enum
import inspect import inspect
import collections import collections
from qutebrowser.utils import usertypes, qtutils from qutebrowser.utils import usertypes, qtutils, log
from qutebrowser.commands import command, cmdexc from qutebrowser.commands import command, cmdexc, argparser
cmd_dict = {} cmd_dict = {}
@ -68,23 +69,19 @@ def arg_or_count(arg, count, default=None, countzero=None):
The value to use. The value to use.
Raise: Raise:
ValueError: If nothing was set or the value couldn't be converted to ValueError: If nothing was set.
an integer.
""" """
if count is not None and arg is not None: if count is not None and arg is not None:
raise ValueError("Both count and argument given!") raise ValueError("Both count and argument given!")
elif arg is not None: elif arg is not None:
try: return arg
return int(arg)
except ValueError:
raise ValueError("Invalid number: {}".format(arg))
elif count is not None: elif count is not None:
if countzero is not None and count == 0: if countzero is not None and count == 0:
return countzero return countzero
else: else:
return int(count) return count
elif default is not None: elif default is not None:
return int(default) return default
else: else:
raise ValueError("Either count or argument have to be set!") raise ValueError("Either count or argument have to be set!")
@ -99,7 +96,6 @@ class register: # pylint: disable=invalid-name
Attributes: Attributes:
instance: The instance to be used as "self", as a dotted string. instance: The instance to be used as "self", as a dotted string.
name: The name (as string) or names (as list) of the command. name: The name (as string) or names (as list) of the command.
nargs: A (minargs, maxargs) tuple of valid argument counts, or an int.
split: Whether to split the arguments. split: Whether to split the arguments.
hide: Whether to hide the command or not. hide: Whether to hide the command or not.
completion: Which completion to use for arguments, as a list of completion: Which completion to use for arguments, as a list of
@ -107,11 +103,18 @@ class register: # pylint: disable=invalid-name
modes/not_modes: List of modes to use/not use. modes/not_modes: List of modes to use/not use.
needs_js: If javascript is needed for this command. needs_js: If javascript is needed for this command.
debug: Whether this is a debugging command (only shown with --debug). debug: Whether this is a debugging command (only shown with --debug).
ignore_args: Whether to ignore the arguments of the function.
Class attributes:
AnnotationInfo: Named tuple for info from an annotation.
""" """
def __init__(self, instance=None, name=None, nargs=None, split=True, AnnotationInfo = collections.namedtuple('AnnotationInfo',
hide=False, completion=None, modes=None, not_modes=None, 'kwargs, typ, name, flag')
needs_js=False, debug=False):
def __init__(self, instance=None, name=None, split=True, hide=False,
completion=None, modes=None, not_modes=None, needs_js=False,
debug=False, ignore_args=False):
"""Save decorator arguments. """Save decorator arguments.
Gets called on parse-time with the decorator arguments. Gets called on parse-time with the decorator arguments.
@ -125,13 +128,13 @@ class register: # pylint: disable=invalid-name
self.name = name self.name = name
self.split = split self.split = split
self.hide = hide self.hide = hide
self.nargs = nargs
self.instance = instance self.instance = instance
self.completion = completion self.completion = completion
self.modes = modes self.modes = modes
self.not_modes = not_modes self.not_modes = not_modes
self.needs_js = needs_js self.needs_js = needs_js
self.debug = debug self.debug = debug
self.ignore_args = ignore_args
if modes is not None: if modes is not None:
for m in modes: for m in modes:
if not isinstance(m, usertypes.KeyMode): if not isinstance(m, usertypes.KeyMode):
@ -155,74 +158,156 @@ class register: # pylint: disable=invalid-name
Return: Return:
The original function (unmodified). The original function (unmodified).
""" """
names = [] names = self._get_names(func)
if self.name is None: log.commands.vdebug("Registering command {}".format(names[0]))
name = func.__name__.lower().replace('_', '-') if any(name in cmd_dict for name in names):
else:
name = self.name
if isinstance(name, str):
mainname = name
names.append(name)
else:
mainname = name[0]
names += name
if mainname in cmd_dict:
raise ValueError("{} is already registered!".format(name)) raise ValueError("{} is already registered!".format(name))
argspec = inspect.getfullargspec(func) has_count, desc, parser = self._inspect_func(func)
if 'self' in argspec.args and self.instance is None:
raise ValueError("{} is a class method, but instance was not "
"given!".format(mainname))
count, nargs = self._get_nargs_count(argspec)
if func.__doc__ is not None:
desc = func.__doc__.splitlines()[0].strip()
else:
desc = ""
cmd = command.Command( cmd = command.Command(
name=mainname, split=self.split, hide=self.hide, nargs=nargs, name=names[0], split=self.split, hide=self.hide, count=has_count,
count=count, desc=desc, instance=self.instance, handler=func, desc=desc, instance=self.instance, handler=func,
completion=self.completion, modes=self.modes, completion=self.completion, modes=self.modes,
not_modes=self.not_modes, needs_js=self.needs_js, debug=self.debug) not_modes=self.not_modes, needs_js=self.needs_js, debug=self.debug,
parser=parser)
for name in names: for name in names:
cmd_dict[name] = cmd cmd_dict[name] = cmd
return func return func
def _get_nargs_count(self, spec): def _get_names(self, func):
"""Get the number of command-arguments and count-support for a func. """Get the name(s) which should be used for the current command.
If the name hasn't been overridden explicitely, the function name is
transformed.
If it has been set, it can either be a string which is
used directly, or an iterable.
Args: Args:
spec: A FullArgSpec as returned by inspect. func: The function to get the name for.
Return: Return:
A (count, (minargs, maxargs)) tuple, with maxargs=None if there are A list of names, with the main name being the first item.
infinite args. count is True if the function supports count, else
False.
Mapping from old nargs format to (minargs, maxargs):
? (0, 1)
N (N, N)
+ (1, None)
* (0, None)
""" """
count = 'count' in spec.args if self.name is None:
# we assume count always has a default (and it should!) return [func.__name__.lower().replace('_', '-')]
if self.nargs is not None: elif isinstance(self.name, str):
# If nargs is overriden, use that. return [self.name]
if isinstance(self.nargs, collections.Iterable):
# Iterable (min, max)
# pylint: disable=unpacking-non-sequence
minargs, maxargs = self.nargs
else: else:
# Single int return self.name
minargs, maxargs = self.nargs, self.nargs
def _inspect_func(self, func):
"""Inspect a function to get useful informations from it.
Args:
func: The function to look at.
Return:
A (has_count, desc, parser) tuple.
has_count: Whether the command supports a count.
desc: The description of the command.
parser: The ArgumentParser to use when parsing the commandline.
"""
signature = inspect.signature(func)
if 'self' in signature.parameters and self.instance is None:
raise ValueError("{} is a class method, but instance was not "
"given!".format(mainname))
has_count = 'count' in signature.parameters
parser = argparser.ArgumentParser()
if func.__doc__ is not None:
desc = func.__doc__.splitlines()[0].strip()
else: else:
defaultcount = (len(spec.defaults) if spec.defaults is not None desc = ""
else 0) if not self.ignore_args:
argcount = len(spec.args) for param in signature.parameters.values():
if 'self' in spec.args: if param.name in ('self', 'count'):
argcount -= 1 continue
minargs = argcount - defaultcount args = []
if spec.varargs is not None: kwargs = {}
maxargs = None annotation_info = self._parse_annotation(param)
if annotation_info.typ is not None:
typ = annotation_info.typ
else: else:
maxargs = argcount - int(count) # -1 if count is defined typ = self._infer_type(param)
return (count, (minargs, maxargs)) kwargs.update(self._type_to_argparse(typ))
kwargs.update(annotation_info.kwargs)
if (param.kind == inspect.Parameter.VAR_POSITIONAL and
'nargs' not in kwargs): # annotation_info overrides it
kwargs['nargs'] = '*'
is_flag = typ == bool
args += self._get_argparse_args(param, annotation_info,
is_flag)
log.commands.vdebug('Adding argument {} of type {} -> '
'args {}, kwargs {}'.format(
param.name, typ, args, kwargs))
parser.add_argument(*args, **kwargs)
return has_count, desc, parser
def _get_argparse_args(self, param, annotation_info, is_flag):
"""Get a list of positional argparse arguments.
Args:
param: The inspect.Parameter instance for the current parameter.
annotation_info: An AnnotationInfo tuple for the parameter.
is_flag: Whether the option is a flag or not.
"""
args = []
name = annotation_info.name or param.name
shortname = annotation_info.flag or param.name[0]
if is_flag:
args.append('--{}'.format(name))
args.append('-{}'.format(shortname))
else:
args.append(name)
return args
def _parse_annotation(self, param):
"""Get argparse arguments and type from a parameter annotation.
Args:
param: A inspect.Parameter instance.
Return:
An AnnotationInfo namedtuple.
kwargs: A dict of keyword args to add to the
argparse.ArgumentParser.add_argument call.
typ: The type to use for this argument.
flag: The short name/flag if overridden.
name: The long name if overridden.
"""
info = {'kwargs': {}, 'typ': None, 'flag': None, 'name': None}
log.commands.vdebug("Parsing annotation {}".format(param.annotation))
if param.annotation is not inspect.Parameter.empty:
if isinstance(param.annotation, dict):
for field in ('type', 'flag', 'name'):
if field in param.annotation:
info[field] = param.annotation[field]
del param.annotation[field]
info['kwargs'] = param.annotation
else:
info['typ'] = param.annotation
return self.AnnotationInfo(**info)
def _infer_type(self, param):
"""Get the type of an argument from its default value."""
if param.default is None or param.default is inspect.Parameter.empty:
return None
else:
return type(param.default)
def _type_to_argparse(self, typ):
"""Get argparse keyword arguments based on a type."""
kwargs = {}
try:
is_enum = issubclass(typ, enum.Enum)
except TypeError:
is_enum = False
if isinstance(typ, tuple):
kwargs['choices'] = typ
elif is_enum:
kwargs['choices'] = [e.name.replace('_', '-') for e in typ]
elif typ is bool:
kwargs['action'] = 'store_true'
elif typ is not None:
kwargs['type'] = typ
return kwargs

View File

@ -19,11 +19,11 @@
"""Contains the Command class, a skeleton for a command.""" """Contains the Command class, a skeleton for a command."""
from PyQt5.QtCore import QCoreApplication, QUrl from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKit import QWebSettings
from qutebrowser.commands import cmdexc from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import log, utils from qutebrowser.utils import log, utils, message
class Command: class Command:
@ -34,7 +34,6 @@ class Command:
name: The main name of the command. name: The main name of the command.
split: Whether to split the arguments. split: Whether to split the arguments.
hide: Whether to hide the arguments or not. hide: Whether to hide the arguments or not.
nargs: A (minargs, maxargs) tuple, maxargs = None if there's no limit.
count: Whether the command supports a count, or not. count: Whether the command supports a count, or not.
desc: The description of the command. desc: The description of the command.
instance: How to get to the "self" argument of the handler. instance: How to get to the "self" argument of the handler.
@ -43,20 +42,20 @@ class Command:
completion: Completions to use for arguments, as a list of strings. completion: Completions to use for arguments, as a list of strings.
needs_js: Whether the command needs javascript enabled needs_js: Whether the command needs javascript enabled
debug: Whether this is a debugging command (only shown with --debug). debug: Whether this is a debugging command (only shown with --debug).
parser: The ArgumentParser to use to parse this command.
""" """
# TODO: # TODO:
# we should probably have some kind of typing / argument casting for args # we should probably have some kind of typing / argument casting for args
# this might be combined with help texts or so as well # this might be combined with help texts or so as well
def __init__(self, name, split, hide, nargs, count, desc, instance, def __init__(self, name, split, hide, count, desc, instance, handler,
handler, completion, modes, not_modes, needs_js, debug): completion, modes, not_modes, needs_js, debug, parser):
# I really don't know how to solve this in a better way, I tried. # I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
self.name = name self.name = name
self.split = split self.split = split
self.hide = hide self.hide = hide
self.nargs = nargs
self.count = count self.count = count
self.desc = desc self.desc = desc
self.instance = instance self.instance = instance
@ -66,15 +65,12 @@ class Command:
self.not_modes = not_modes self.not_modes = not_modes
self.needs_js = needs_js self.needs_js = needs_js
self.debug = debug self.debug = debug
self.parser = parser
def check(self, args): def _check_prerequisites(self):
"""Check if the argument count is valid and the command is permitted. """Check if the command is permitted to run currently.
Args:
args: The supplied arguments
Raise: Raise:
ArgumentCountError if the argument count is wrong.
PrerequisitesError if the command can't be called currently. PrerequisitesError if the command can't be called currently.
""" """
# We don't use modeman.instance() here to avoid a circular import # We don't use modeman.instance() here to avoid a circular import
@ -94,20 +90,6 @@ class Command:
QWebSettings.JavascriptEnabled): QWebSettings.JavascriptEnabled):
raise cmdexc.PrerequisitesError( raise cmdexc.PrerequisitesError(
"{}: This command needs javascript enabled.".format(self.name)) "{}: This command needs javascript enabled.".format(self.name))
if self.nargs[1] is None and self.nargs[0] <= len(args):
pass
elif self.nargs[0] <= len(args) <= self.nargs[1]:
pass
else:
if self.nargs[0] == self.nargs[1]:
argcnt = str(self.nargs[0])
elif self.nargs[1] is None:
argcnt = '{}-inf'.format(self.nargs[0])
else:
argcnt = '{}-{}'.format(self.nargs[0], self.nargs[1])
raise cmdexc.ArgumentCountError(
"{}: {} args expected, but got {}".format(self.name, argcnt,
len(args)))
def run(self, args=None, count=None): def run(self, args=None, count=None):
"""Run the command. """Run the command.
@ -120,23 +102,29 @@ class Command:
""" """
dbgout = ["command called:", self.name] dbgout = ["command called:", self.name]
if args: if args:
dbgout += args dbgout.append(str(args))
if count is not None: if count is not None:
dbgout.append("(count={})".format(count)) dbgout.append("(count={})".format(count))
log.commands.debug(' '.join(dbgout)) log.commands.debug(' '.join(dbgout))
posargs = []
kwargs = {} kwargs = {}
app = QCoreApplication.instance() app = QCoreApplication.instance()
# Replace variables (currently only {url}) try:
new_args = [] namespace = self.parser.parse_args(args)
for arg in args: except argparser.ArgumentParserError as e:
if arg == '{url}': message.error(str(e))
urlstr = app.mainwindow.tabs.current_url().toString( return
QUrl.FullyEncoded | QUrl.RemovePassword)
new_args.append(urlstr) for name, arg in vars(namespace).items():
if isinstance(arg, list):
# If we got a list, we assume that's our *args, so we don't add
# it to kwargs.
# FIXME: This approach is rather naive, but for now it works.
posargs += arg
else: else:
new_args.append(arg) kwargs[name] = arg
if self.instance is not None: if self.instance is not None:
# Add the 'self' parameter. # Add the 'self' parameter.
@ -144,9 +132,12 @@ class Command:
obj = app obj = app
else: else:
obj = utils.dotted_getattr(app, self.instance) obj = utils.dotted_getattr(app, self.instance)
new_args.insert(0, obj) posargs.insert(0, obj)
if count is not None and self.count: if count is not None and self.count:
kwargs = {'count': count} kwargs = {'count': count}
self.handler(*new_args, **kwargs) self._check_prerequisites()
log.commands.debug('posargs: {}'.format(posargs))
log.commands.debug('kwargs: {}'.format(kwargs))
self.handler(*posargs, **kwargs)

View File

@ -19,7 +19,7 @@
"""Module containing command managers (SearchRunner and CommandRunner).""" """Module containing command managers (SearchRunner and CommandRunner)."""
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QCoreApplication, QUrl
from PyQt5.QtWebKitWidgets import QWebPage from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config from qutebrowser.config import config
@ -198,14 +198,18 @@ class CommandRunner:
parts = text.strip().split(maxsplit=1) parts = text.strip().split(maxsplit=1)
if not parts: if not parts:
raise cmdexc.NoSuchCommandError("No command given") raise cmdexc.NoSuchCommandError("No command given")
elif len(parts) > 1:
cmdstr, argstr = parts
else:
cmdstr = parts[0] cmdstr = parts[0]
argstr = None
if aliases: if aliases:
new_cmd = self._get_alias(text, alias_no_args) new_cmd = self._get_alias(text, alias_no_args)
if new_cmd is not None: if new_cmd is not None:
log.commands.debug("Re-parsing with '{}'.".format(new_cmd)) log.commands.debug("Re-parsing with '{}'.".format(new_cmd))
return self.parse(new_cmd, aliases=False) return self.parse(new_cmd, aliases=False)
try: try:
cmd = cmdutils.cmd_dict[cmdstr] self._cmd = cmdutils.cmd_dict[cmdstr]
except KeyError: except KeyError:
if fallback: if fallback:
parts = text.split(' ') parts = text.split(' ')
@ -215,36 +219,38 @@ class CommandRunner:
else: else:
raise cmdexc.NoSuchCommandError( raise cmdexc.NoSuchCommandError(
'{}: no such command'.format(cmdstr)) '{}: no such command'.format(cmdstr))
if len(parts) == 1: if argstr is None:
args = [] self._args = []
elif cmd.split: elif self._cmd.split:
args = utils.safe_shlex_split(parts[1]) self._args = utils.safe_shlex_split(argstr)
else: else:
args = parts[1].split(maxsplit=cmd.nargs[0] - 1) # If split=False, we still want to split the flags, but not
self._cmd = cmd # everything after that.
self._args = args # We first split the arg string and check the index of the first
retargs = args[:] # non-flag args, then we re-split again properly.
# example:
#
# input: "--foo -v bar baz"
# first split: ['--foo', '-v', 'bar', 'baz']
# 0 1 2 3
# second split: ['--foo', '-v', 'bar baz']
# (maxsplit=2)
split_args = argstr.split()
for i, arg in enumerate(split_args):
if not arg.startswith('-'):
self._args = argstr.split(maxsplit=i)
break
else:
# If there are only flags, we got it right on the first try
# already.
self._args = split_args
retargs = self._args[:]
if text.endswith(' '): if text.endswith(' '):
retargs.append('') retargs.append('')
return [cmdstr] + retargs return [cmdstr] + retargs
def _check(self):
"""Check if the argument count for the command is correct."""
self._cmd.check(self._args)
def _run(self, count=None):
"""Run a command with an optional count.
Args:
count: Count to pass to the command.
"""
if count is not None:
self._cmd.run(self._args, count=count)
else:
self._cmd.run(self._args)
def run(self, text, count=None): def run(self, text, count=None):
"""Parse a command from a line of text. """Parse a command from a line of text and run it.
Args: Args:
text: The text to parse. text: The text to parse.
@ -255,8 +261,15 @@ class CommandRunner:
self.run(sub, count) self.run(sub, count)
return return
self.parse(text) self.parse(text)
self._check() app = QCoreApplication.instance()
self._run(count=count) cur_url = app.mainwindow.tabs.current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
self._args = [cur_url if e == '{url}' else e for e in self._args]
if count is not None:
self._cmd.run(self._args, count=count)
else:
self._cmd.run(self._args)
@pyqtSlot(str, int) @pyqtSlot(str, int)
def run_safely(self, text, count=None): def run_safely(self, text, count=None):

View File

@ -336,7 +336,7 @@ class ConfigManager(QObject):
@cmdutils.register(name='set', instance='config', @cmdutils.register(name='set', instance='config',
completion=[Completion.section, Completion.option, completion=[Completion.section, Completion.option,
Completion.value]) Completion.value])
def set_wrapper(self, sectname, optname, value): def set_command(self, sectname, optname, value, temp=False):
"""Set an option. """Set an option.
// //
@ -347,36 +347,15 @@ class ConfigManager(QObject):
sectname: The section where the option is in. sectname: The section where the option is in.
optname: The name of the option. optname: The name of the option.
value: The value to set. value: The value to set.
temp: Set value temporarely.
""" """
try: try:
self.set('conf', sectname, optname, value) self.set('temp' if temp else 'conf', sectname, optname, value)
except (NoOptionError, NoSectionError, configtypes.ValidationError, except (NoOptionError, NoSectionError, configtypes.ValidationError,
ValueError) as e: ValueError) as e:
raise cmdexc.CommandError("set: {} - {}".format( raise cmdexc.CommandError("set: {} - {}".format(
e.__class__.__name__, e)) e.__class__.__name__, e))
@cmdutils.register(name='set-temp', instance='config',
completion=[Completion.section, Completion.option,
Completion.value])
def set_temp_wrapper(self, sectname, optname, value):
"""Set a temporary option.
//
Wrapper for self.set() to output exceptions in the status bar.
Args:
sectname: The section where the option is in.
optname: The name of the option.
value: The value to set.
"""
try:
self.set('temp', sectname, optname, value)
except (NoOptionError, NoSectionError,
configtypes.ValidationError) as e:
raise cmdexc.CommandError("set: {} - {}".format(
e.__class__.__name__, e))
def set(self, layer, sectname, optname, value): def set(self, layer, sectname, optname, value):
"""Set an option. """Set an option.

View File

@ -585,11 +585,11 @@ DATA = collections.OrderedDict([
typ.KeyBindingName(), typ.KeyBinding(), typ.KeyBindingName(), typ.KeyBinding(),
('o', 'set-cmd-text ":open "'), ('o', 'set-cmd-text ":open "'),
('go', 'set-cmd-text :open {url}'), ('go', 'set-cmd-text :open {url}'),
('O', 'set-cmd-text ":open-tab "'), ('O', 'set-cmd-text ":open -t "'),
('gO', 'set-cmd-text :open-tab {url}'), ('gO', 'set-cmd-text :open -t {url}'),
('xo', 'set-cmd-text ":open-tab-bg "'), ('xo', 'set-cmd-text ":open -b "'),
('xO', 'set-cmd-text :open-tab-bg {url}'), ('xO', 'set-cmd-text :open -b {url}'),
('ga', 'open-tab about:blank'), ('ga', 'open -t about:blank'),
('d', 'tab-close'), ('d', 'tab-close'),
('co', 'tab-only'), ('co', 'tab-only'),
('T', 'tab-focus'), ('T', 'tab-focus'),
@ -608,8 +608,8 @@ DATA = collections.OrderedDict([
(';I', 'hint images tab'), (';I', 'hint images tab'),
('.i', 'hint images tab-bg'), ('.i', 'hint images tab-bg'),
(';o', 'hint links fill :open {hint-url}'), (';o', 'hint links fill :open {hint-url}'),
(';O', 'hint links fill :open-tab {hint-url}'), (';O', 'hint links fill :open -t {hint-url}'),
('.o', 'hint links fill :open-tab-bg {hint-url}'), ('.o', 'hint links fill :open -b {hint-url}'),
(';y', 'hint links yank'), (';y', 'hint links yank'),
(';Y', 'hint links yank-primary'), (';Y', 'hint links yank-primary'),
(';r', 'hint links rapid'), (';r', 'hint links rapid'),
@ -619,33 +619,33 @@ DATA = collections.OrderedDict([
('k', 'scroll 0 -50'), ('k', 'scroll 0 -50'),
('l', 'scroll 50 0'), ('l', 'scroll 50 0'),
('u', 'undo'), ('u', 'undo'),
('gg', 'scroll-perc-y 0'), ('gg', 'scroll-perc 0'),
('G', 'scroll-perc-y'), ('G', 'scroll-perc'),
('n', 'search-next'), ('n', 'search-next'),
('N', 'search-prev'), ('N', 'search-prev'),
('i', 'enter-mode insert'), ('i', 'enter-mode insert'),
('yy', 'yank'), ('yy', 'yank'),
('yY', 'yank sel'), ('yY', 'yank -s'),
('yt', 'yank-title'), ('yt', 'yank -t'),
('yT', 'yank-title sel'), ('yT', 'yank -ts'),
('pp', 'paste'), ('pp', 'paste'),
('pP', 'paste sel'), ('pP', 'paste -s'),
('Pp', 'paste-tab'), ('Pp', 'paste -t'),
('PP', 'paste-tab sel'), ('PP', 'paste -ts'),
('m', 'quickmark-save'), ('m', 'quickmark-save'),
('b', 'set-cmd-text ":quickmark-load "'), ('b', 'set-cmd-text ":quickmark-load "'),
('B', 'set-cmd-text ":quickmark-load-tab "'), ('B', 'set-cmd-text ":quickmark-load -t "'),
('sf', 'save'), ('sf', 'save'),
('ss', 'set-cmd-text ":set "'), ('ss', 'set-cmd-text ":set "'),
('sl', 'set-cmd-text ":set-temp "'), ('sl', 'set-cmd-text ":set -t"'),
('sk', 'set-cmd-text ":set keybind "'), ('sk', 'set-cmd-text ":set keybind "'),
('-', 'zoom-out'), ('-', 'zoom-out'),
('+', 'zoom-in'), ('+', 'zoom-in'),
('=', 'zoom'), ('=', 'zoom'),
('[[', 'prev-page'), ('[[', 'prev-page'),
(']]', 'next-page'), (']]', 'next-page'),
('{{', 'prev-page-tab'), ('{{', 'prev-page -t'),
('}}', 'next-page-tab'), ('}}', 'next-page -t'),
('wi', 'inspector'), ('wi', 'inspector'),
('gd', 'download-page'), ('gd', 'download-page'),
('ad', 'cancel-download'), ('ad', 'cancel-download'),
@ -654,7 +654,7 @@ DATA = collections.OrderedDict([
('<Ctrl-Q>', 'quit'), ('<Ctrl-Q>', 'quit'),
('<Ctrl-Shift-T>', 'undo'), ('<Ctrl-Shift-T>', 'undo'),
('<Ctrl-W>', 'tab-close'), ('<Ctrl-W>', 'tab-close'),
('<Ctrl-T>', 'open-tab about:blank'), ('<Ctrl-T>', 'open -t about:blank'),
('<Ctrl-F>', 'scroll-page 0 1'), ('<Ctrl-F>', 'scroll-page 0 1'),
('<Ctrl-B>', 'scroll-page 0 -1'), ('<Ctrl-B>', 'scroll-page 0 -1'),
('<Ctrl-D>', 'scroll-page 0 0.5'), ('<Ctrl-D>', 'scroll-page 0 0.5'),

View File

@ -32,7 +32,7 @@ from qutebrowser.config import config, style
@cmdutils.register(debug=True) @cmdutils.register(debug=True)
def debug_crash(typ='exception'): def debug_crash(typ : ('exception', 'segfault') = 'exception'):
"""Crash for debugging purposes. """Crash for debugging purposes.
Args: Args:

View File

@ -35,15 +35,14 @@ def init():
_commandrunner = runners.CommandRunner() _commandrunner = runners.CommandRunner()
@cmdutils.register(nargs=(2, None)) @cmdutils.register()
def later(ms, *command): def later(ms : int, *command : {'nargs': '+'}):
"""Execute a command after some time. """Execute a command after some time.
Args: Args:
ms: How many milliseconds to wait. ms: How many milliseconds to wait.
command: The command/args to run. command: The command/args to run.
""" """
ms = int(ms)
timer = usertypes.Timer(name='later') timer = usertypes.Timer(name='later')
timer.setSingleShot(True) timer.setSingleShot(True)
if ms < 0: if ms < 0:

View File

@ -141,7 +141,7 @@ class MainWindow(QWidget):
if rect.isValid(): if rect.isValid():
self.completion.setGeometry(rect) self.completion.setGeometry(rect)
@cmdutils.register(instance='mainwindow', name=['quit', 'q'], nargs=0) @cmdutils.register(instance='mainwindow', name=['quit', 'q'])
def close(self): def close(self):
"""Quit qutebrowser. """Quit qutebrowser.

View File

@ -221,20 +221,20 @@ class WebView(QWebView):
e: The QMouseEvent. e: The QMouseEvent.
""" """
if self._force_open_target is not None: if self._force_open_target is not None:
self.open_target = self._force_open_target self._open_target = self._force_open_target
self._force_open_target = None self._force_open_target = None
log.mouse.debug("Setting force target: {}".format( log.mouse.debug("Setting force target: {}".format(
self.open_target)) self._open_target))
elif (e.button() == Qt.MidButton or elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier): e.modifiers() & Qt.ControlModifier):
if config.get('tabs', 'background-tabs'): if config.get('tabs', 'background-tabs'):
self.open_target = usertypes.ClickTarget.tab_bg self._open_target = usertypes.ClickTarget.tab_bg
else: else:
self.open_target = usertypes.ClickTarget.tab self._open_target = usertypes.ClickTarget.tab
log.mouse.debug("Middle click, setting target: {}".format( log.mouse.debug("Middle click, setting target: {}".format(
self.open_target)) self._open_target))
else: else:
self.open_target = usertypes.ClickTarget.normal self._open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target") log.mouse.debug("Normal click, setting normal target")
def shutdown(self): def shutdown(self):