summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2014-09-03 10:47:27 +0200
committerFlorian Bruhin <git@the-compiler.org>2014-09-08 07:36:17 +0200
commit57d51ad9bb88f05dced3c47a68688bfebc90e697 (patch)
tree15c7018b99490cf735cfe8e20cb558ef3ee0ff92
parentd836e26107733e558a8e8553f6cc977d8aeac18c (diff)
downloadqutebrowser-57d51ad9bb88f05dced3c47a68688bfebc90e697.tar.gz
qutebrowser-57d51ad9bb88f05dced3c47a68688bfebc90e697.zip
Lots of fixes for new command system.
Squashed commit: - Fix getting current URL - Get rid of *args for hints. - Make enums work. - Fix moving commands to utilcmds. - Fix enums in argparse - Fix arg splitting for hints. - Fix default enum args. - Fix argument splitting for hints if None is given. - Fix set_cmd_text with flags and fix {url}. - Fix unittests - Fix tuple types for arguments. - Fix scroll-page. - Fix lint - Fix open_target. - Others
-rw-r--r--.flake83
-rw-r--r--qutebrowser/browser/commands.py23
-rw-r--r--qutebrowser/browser/hints.py22
-rw-r--r--qutebrowser/commands/argparser.py50
-rw-r--r--qutebrowser/commands/cmdexc.py7
-rw-r--r--qutebrowser/commands/cmdutils.py110
-rw-r--r--qutebrowser/commands/command.py18
-rw-r--r--qutebrowser/commands/runners.py36
-rw-r--r--qutebrowser/config/configdata.py14
-rw-r--r--qutebrowser/test/utils/test_debug.py6
-rw-r--r--qutebrowser/test/utils/test_utils.py21
-rw-r--r--qutebrowser/utils/debug.py51
-rw-r--r--qutebrowser/utils/utilcmds.py30
-rw-r--r--qutebrowser/utils/utils.py9
-rw-r--r--qutebrowser/widgets/statusbar/command.py15
-rw-r--r--qutebrowser/widgets/tabbedbrowser.py8
-rw-r--r--qutebrowser/widgets/webview.py12
17 files changed, 258 insertions, 177 deletions
diff --git a/.flake8 b/.flake8
index 0c7c865d4..78c542d81 100644
--- a/.flake8
+++ b/.flake8
@@ -11,7 +11,8 @@
# E222: Multiple spaces after operator
# F811: Redifiniton
# W292: No newline at end of file
+# E701: multiple statements on one line
# E702: multiple statements on one line
-ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E702
+ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E701,E702
max_complexity = 12
exclude = ez_setup.py
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 9006c0fbc..0a54db600 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -233,7 +233,7 @@ class CommandDispatcher:
else:
diag = QPrintDialog()
diag.setAttribute(Qt.WA_DeleteOnClose)
- diag.open(lambda: tab.print(printdiag.printer()))
+ diag.open(lambda: tab.print(diag.printer()))
@cmdutils.register(instance='mainwindow.tabs.cmd')
def back(self, count=1):
@@ -257,7 +257,7 @@ class CommandDispatcher:
@cmdutils.register(instance='mainwindow.tabs.cmd')
def hint(self, group=webelem.Group.all, target=hints.Target.normal,
- *args : {'nargs': '*'}):
+ args=None):
"""Start hinting.
Args:
@@ -286,7 +286,7 @@ class CommandDispatcher:
link.
- `spawn`: Spawn a command.
- *args: Arguments for spawn/userscript/fill.
+ args: Arguments for spawn/userscript/fill.
- With `spawn`: The executable and arguments to spawn.
`{hint-url}` will get replaced by the selected
@@ -301,7 +301,7 @@ class CommandDispatcher:
if frame is None:
raise cmdexc.CommandError("No frame focused!")
widget.hintmanager.start(frame, self._tabs.current_url(), group,
- target, *args)
+ target, args)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def follow_hint(self):
@@ -333,7 +333,7 @@ class CommandDispatcher:
self._prevnext(prev=False, newtab=tab)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
- def scroll(self, dx : float, dy : float, count=1):
+ def scroll(self, dx: float, dy: float, count=1):
"""Scroll the current tab by 'count * dx/dy'.
Args:
@@ -348,8 +348,8 @@ class CommandDispatcher:
self._current_widget().page().currentFrame().scroll(dx, dy)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
- def scroll_perc(self, perc : float = None,
- horizontal : {'flag': 'x'} = False, count=None):
+ def scroll_perc(self, perc: float=None,
+ 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.
@@ -364,7 +364,7 @@ class CommandDispatcher:
Qt.Horizontal if horizontal else Qt.Vertical)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
- def scroll_page(self, x : int, y : int, count=1):
+ def scroll_page(self, x: float, y: float, count=1):
"""Scroll the frame page-wise.
Args:
@@ -402,7 +402,8 @@ class CommandDispatcher:
target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, s))
clipboard.setText(s, mode)
- message.info("URL yanked to {}".format(target))
+ what = 'Title' if title else 'URL'
+ message.info("{} yanked to {}".format(what, target))
@cmdutils.register(instance='mainwindow.tabs.cmd')
def zoom_in(self, count=1):
@@ -518,7 +519,7 @@ class CommandDispatcher:
widget.openurl(url)
@cmdutils.register(instance='mainwindow.tabs.cmd')
- def tab_focus(self, index : int = None, count=None):
+ def tab_focus(self, index: (int, 'last')=None, count=None):
"""Select the tab given as argument/[count].
Args:
@@ -542,7 +543,7 @@ class CommandDispatcher:
idx))
@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.
Args:
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index 1583fa331..f4676f337 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -20,6 +20,7 @@
"""A HintManager to draw hints over links."""
import math
+import shlex
import subprocess
import collections
@@ -472,7 +473,7 @@ class HintManager(QObject):
self.openurl.emit(url, newtab)
def start(self, mainframe, baseurl, group=webelem.Group.all,
- target=Target.normal, *args):
+ target=Target.normal, args=None):
"""Start hinting.
Args:
@@ -480,7 +481,7 @@ class HintManager(QObject):
baseurl: URL of the current page.
group: Which group of elements to hint.
target: What to do with the link. See attribute docstring.
- *args: Arguments for userscript/download
+ args: Arguments for userscript/download
Emit:
hint_strings_updated: Emitted to update keypraser.
@@ -493,14 +494,13 @@ class HintManager(QObject):
# on_mode_left, we are extra careful here.
raise ValueError("start() was called with frame=None")
if target in (Target.userscript, Target.spawn, Target.fill):
- if not args:
+ if args is None:
raise cmdexc.CommandError(
- "Additional arguments are required with target "
- "userscript/spawn/fill.")
+ "'args' is required with target userscript/spawn/fill.")
else:
- if args:
+ if args is not None:
raise cmdexc.CommandError(
- "Arguments are only allowed with target userscript/spawn.")
+ "'args' is only allowed with target userscript/spawn.")
elems = []
ctx = HintContext()
ctx.frames = webelem.get_child_frames(mainframe)
@@ -514,7 +514,13 @@ class HintManager(QObject):
raise cmdexc.CommandError("No elements found.")
ctx.target = target
ctx.baseurl = baseurl
- ctx.args = args
+ if args is None:
+ ctx.args = None
+ else:
+ try:
+ ctx.args = shlex.split(args)
+ except ValueError as e:
+ raise cmdexc.CommandError("Could not split args: {}".format(e))
message.instance().set_text(self.HINT_TEXTS[target])
strings = self._hint_strings(visible_elems)
for e, string in zip(visible_elems, strings):
diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py
index a31f0988d..69ef61920 100644
--- a/qutebrowser/commands/argparser.py
+++ b/qutebrowser/commands/argparser.py
@@ -19,8 +19,12 @@
"""argparse.ArgumentParser subclass to parse qutebrowser commands."""
+
import argparse
+from qutebrowser.commands import cmdexc
+from qutebrowser.utils import utils
+
class ArgumentParserError(Exception):
@@ -29,6 +33,8 @@ class ArgumentParserError(Exception):
class ArgumentParser(argparse.ArgumentParser):
+ """Subclass ArgumentParser to be more suitable for runtime parsing."""
+
def __init__(self):
super().__init__(add_help=False)
@@ -37,4 +43,46 @@ class ArgumentParser(argparse.ArgumentParser):
'Status: {}, message: {}'.format(status, msg))
def error(self, msg):
- raise ArgumentParserError(msg)
+ raise ArgumentParserError(msg[0].upper() + msg[1:])
+
+
+def enum_getter(enum):
+ """Function factory to get an enum getter."""
+
+ def _get_enum_item(key):
+ """Helper function to get an enum item.
+
+ Passes through existing items unmodified.
+ """
+ if isinstance(key, enum):
+ return key
+ try:
+ return enum[key.replace('-', '_')]
+ except KeyError:
+ raise cmdexc.ArgumentTypeError("Invalid value {}.".format(key))
+
+ return _get_enum_item
+
+
+def multitype_conv(tpl):
+ """Function factory to get a type converter for a choice of types."""
+
+ def _convert(value):
+ """Convert a value according to an iterable of possible arg types."""
+ for typ in tpl:
+ if isinstance(typ, str):
+ if value == typ:
+ return value
+ elif utils.is_enum(typ):
+ return enum_getter(typ)(value)
+ elif callable(typ):
+ # int, float, etc.
+ if isinstance(value, typ):
+ return value
+ try:
+ return typ(value)
+ except ValueError:
+ pass
+ raise cmdexc.ArgumentTypeError('Invalid value {}.'.format(value))
+
+ return _convert
diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py
index c5dd7f214..82334f919 100644
--- a/qutebrowser/commands/cmdexc.py
+++ b/qutebrowser/commands/cmdexc.py
@@ -49,6 +49,13 @@ class ArgumentCountError(CommandMetaError):
pass
+class ArgumentTypeError(CommandMetaError):
+
+ """Raised when an argument had an invalid type."""
+
+ pass
+
+
class PrerequisitesError(CommandMetaError):
"""Raised when a cmd can't be used because some prerequisites aren't met.
diff --git a/qutebrowser/commands/cmdutils.py b/qutebrowser/commands/cmdutils.py
index 7302fbfec..818b43a3c 100644
--- a/qutebrowser/commands/cmdutils.py
+++ b/qutebrowser/commands/cmdutils.py
@@ -23,11 +23,11 @@ Module attributes:
cmd_dict: A mapping from command-strings to command objects.
"""
-import enum
import inspect
import collections
-from qutebrowser.utils import usertypes, qtutils, log, debug
+from qutebrowser.utils import usertypes, qtutils, log, utils
+from qutebrowser.utils import debug as debugutils
from qutebrowser.commands import command, cmdexc, argparser
cmd_dict = {}
@@ -160,15 +160,16 @@ class register: # pylint: disable=invalid-name
"""
names = self._get_names(func)
log.commands.vdebug("Registering command {}".format(names[0]))
- if any(name in cmd_dict for name in names):
- raise ValueError("{} is already registered!".format(name))
- has_count, desc, parser = self._inspect_func(func)
+ for name in names:
+ if name in cmd_dict:
+ raise ValueError("{} is already registered!".format(name))
+ has_count, desc, parser, type_conv = self._inspect_func(func)
cmd = command.Command(
name=names[0], split=self.split, hide=self.hide, count=has_count,
desc=desc, instance=self.instance, handler=func,
completion=self.completion, modes=self.modes,
- not_modes=self.not_modes, needs_js=self.needs_js, debug=self.debug,
- parser=parser)
+ not_modes=self.not_modes, needs_js=self.needs_js,
+ is_debug=self.debug, parser=parser, type_conv=type_conv)
for name in names:
cmd_dict[name] = cmd
return func
@@ -202,15 +203,17 @@ class register: # pylint: disable=invalid-name
func: The function to look at.
Return:
- A (has_count, desc, parser) tuple.
+ A (has_count, desc, parser, type_conv) tuple.
has_count: Whether the command supports a count.
desc: The description of the command.
parser: The ArgumentParser to use when parsing the commandline.
+ type_conv: A mapping of args to type converter callables.
"""
+ type_conv = {}
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))
+ "given!".format(self.name[0]))
has_count = 'count' in signature.parameters
parser = argparser.ArgumentParser()
if func.__doc__ is not None:
@@ -224,43 +227,64 @@ class register: # pylint: disable=invalid-name
args = []
kwargs = {}
annotation_info = self._parse_annotation(param)
- if annotation_info.typ is not None:
- typ = annotation_info.typ
- else:
- typ = self._infer_type(param)
- kwargs.update(self._type_to_argparse(typ))
+ kwargs.update(self._param_to_argparse_kw(
+ param, annotation_info))
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)
- callsig = debug.format_call(parser.add_argument, args,
- kwargs, full=False)
+ args += self._param_to_argparse_pos(param, annotation_info)
+ typ = self._get_type(param, annotation_info)
+ if utils.is_enum(typ):
+ type_conv[param.name] = argparser.enum_getter(typ)
+ elif isinstance(typ, tuple):
+ type_conv[param.name] = argparser.multitype_conv(typ)
+ callsig = debugutils.format_call(parser.add_argument, args,
+ kwargs, full=False)
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
param.name, typ, callsig))
parser.add_argument(*args, **kwargs)
- return has_count, desc, parser
+ return has_count, desc, parser, type_conv
- def _get_argparse_args(self, param, annotation_info, is_flag):
+ def _param_to_argparse_pos(self, param, annotation_info):
"""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:
+ if self._get_type(param, annotation_info) == bool:
args.append('--{}'.format(name))
args.append('-{}'.format(shortname))
else:
args.append(name)
return args
+ def _param_to_argparse_kw(self, param, annotation_info):
+ """Get argparse keyword arguments for a parameter.
+
+ Args:
+ param: The inspect.Parameter object to get the args for.
+ annotation_info: An AnnotationInfo tuple for the parameter.
+ """
+ kwargs = {}
+ typ = self._get_type(param, annotation_info)
+ if isinstance(typ, tuple):
+ pass
+ elif utils.is_enum(typ):
+ 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
+
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
+ kwargs['nargs'] = '*'
+ elif typ is not bool and param.default is not inspect.Parameter.empty:
+ kwargs['default'] = param.default
+ kwargs['nargs'] = '?'
+ return kwargs
+
def _parse_annotation(self, param):
"""Get argparse arguments and type from a parameter annotation.
@@ -276,8 +300,9 @@ class register: # pylint: disable=invalid-name
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:
+ log.commands.vdebug("Parsing annotation {}".format(
+ param.annotation))
if isinstance(param.annotation, dict):
for field in ('type', 'flag', 'name'):
if field in param.annotation:
@@ -288,27 +313,16 @@ class register: # pylint: disable=invalid-name
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:
+ def _get_type(self, param, annotation_info):
+ """Get the type of an argument from its default value or annotation.
+
+ Args:
+ param: The inspect.Parameter to look at.
+ annotation_info: An AnnotationInfo tuple which overrides the type.
+ """
+ if annotation_info.typ is not None:
+ return annotation_info.typ
+ elif 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
diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py
index afb3b30e7..72af36f37 100644
--- a/qutebrowser/commands/command.py
+++ b/qutebrowser/commands/command.py
@@ -43,6 +43,7 @@ class Command:
needs_js: Whether the command needs javascript enabled
debug: Whether this is a debugging command (only shown with --debug).
parser: The ArgumentParser to use to parse this command.
+ type_conv: A mapping of conversion functions for arguments.
"""
# TODO:
@@ -50,7 +51,8 @@ class Command:
# this might be combined with help texts or so as well
def __init__(self, name, split, hide, count, desc, instance, handler,
- completion, modes, not_modes, needs_js, debug, parser):
+ completion, modes, not_modes, needs_js, is_debug, parser,
+ type_conv):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-arguments
self.name = name
@@ -64,8 +66,9 @@ class Command:
self.modes = modes
self.not_modes = not_modes
self.needs_js = needs_js
- self.debug = debug
+ self.debug = is_debug
self.parser = parser
+ self.type_conv = type_conv
def _check_prerequisites(self):
"""Check if the command is permitted to run currently.
@@ -114,7 +117,7 @@ class Command:
try:
namespace = self.parser.parse_args(args)
except argparser.ArgumentParserError as e:
- message.error(str(e))
+ message.error('{}: {}'.format(self.name, e))
return
for name, arg in vars(namespace).items():
@@ -124,6 +127,12 @@ class Command:
# FIXME: This approach is rather naive, but for now it works.
posargs += arg
else:
+ if name in self.type_conv:
+ # We convert enum types after getting the values from
+ # argparse, because argparse's choices argument is
+ # processed after type conversation, which is not what we
+ # want.
+ arg = self.type_conv[name](arg)
kwargs[name] = arg
if self.instance is not None:
@@ -140,4 +149,7 @@ class Command:
self._check_prerequisites()
log.commands.debug('Calling {}'.format(
debug.format_call(self.handler, posargs, kwargs)))
+ # FIXME this won't work properly if some arguments are required to be
+ # positional, e.g.:
+ # def fun(one=True, two=False, *args)
self.handler(*posargs, **kwargs)
diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py
index 7ad83ed9d..02ac4477e 100644
--- a/qutebrowser/commands/runners.py
+++ b/qutebrowser/commands/runners.py
@@ -27,6 +27,20 @@ from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, log, utils
+def replace_variables(arglist):
+ """Utility function to replace variables like {url} in a list of args."""
+ args = []
+ for arg in arglist:
+ if arg == '{url}':
+ app = QCoreApplication.instance()
+ url = app.mainwindow.tabs.current_url().toString(
+ QUrl.FullyEncoded | QUrl.RemovePassword)
+ args.append(url)
+ else:
+ args.append(arg)
+ return args
+
+
class SearchRunner(QObject):
"""Run searches on webpages.
@@ -219,6 +233,14 @@ class CommandRunner:
else:
raise cmdexc.NoSuchCommandError(
'{}: no such command'.format(cmdstr))
+ self._split_args(argstr)
+ retargs = self._args[:]
+ if text.endswith(' '):
+ retargs.append('')
+ return [cmdstr] + retargs
+
+ def _split_args(self, argstr):
+ """Split the arguments from an arg string."""
if argstr is None:
self._args = []
elif self._cmd.split:
@@ -244,10 +266,6 @@ class CommandRunner:
# If there are only flags, we got it right on the first try
# already.
self._args = split_args
- retargs = self._args[:]
- if text.endswith(' '):
- retargs.append('')
- return [cmdstr] + retargs
def run(self, text, count=None):
"""Parse a command from a line of text and run it.
@@ -261,15 +279,11 @@ class CommandRunner:
self.run(sub, count)
return
self.parse(text)
- app = QCoreApplication.instance()
- 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]
+ args = replace_variables(self._args)
if count is not None:
- self._cmd.run(self._args, count=count)
+ self._cmd.run(args, count=count)
else:
- self._cmd.run(self._args)
-
+ self._cmd.run(args)
@pyqtSlot(str, int)
def run_safely(self, text, count=None):
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index 7afff556c..263c6cb8e 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -584,11 +584,11 @@ DATA = collections.OrderedDict([
('keybind', sect.ValueList(
typ.KeyBindingName(), typ.KeyBinding(),
('o', 'set-cmd-text ":open "'),
- ('go', 'set-cmd-text :open {url}'),
+ ('go', 'set-cmd-text ":open {url}"'),
('O', 'set-cmd-text ":open -t "'),
- ('gO', 'set-cmd-text :open -t {url}'),
+ ('gO', 'set-cmd-text ":open -t {url}"'),
('xo', 'set-cmd-text ":open -b "'),
- ('xO', 'set-cmd-text :open -b {url}'),
+ ('xO', 'set-cmd-text ":open -b {url}"'),
('ga', 'open -t about:blank'),
('d', 'tab-close'),
('co', 'tab-only'),
@@ -607,9 +607,9 @@ DATA = collections.OrderedDict([
(';i', 'hint images'),
(';I', 'hint images tab'),
('.i', 'hint images tab-bg'),
- (';o', 'hint links fill :open {hint-url}'),
- (';O', 'hint links fill :open -t {hint-url}'),
- ('.o', 'hint links fill :open -b {hint-url}'),
+ (';o', 'hint links fill ":open {hint-url}"'),
+ (';O', 'hint links fill ":open -t {hint-url}"'),
+ ('.o', 'hint links fill ":open -b {hint-url}"'),
(';y', 'hint links yank'),
(';Y', 'hint links yank-primary'),
(';r', 'hint links rapid'),
@@ -637,7 +637,7 @@ DATA = collections.OrderedDict([
('B', 'set-cmd-text ":quickmark-load -t "'),
('sf', 'save'),
('ss', 'set-cmd-text ":set "'),
- ('sl', 'set-cmd-text ":set -t"'),
+ ('sl', 'set-cmd-text ":set -t "'),
('sk', 'set-cmd-text ":set keybind "'),
('-', 'zoom-out'),
('+', 'zoom-in'),
diff --git a/qutebrowser/test/utils/test_debug.py b/qutebrowser/test/utils/test_debug.py
index 6f9e70a05..5d743595a 100644
--- a/qutebrowser/test/utils/test_debug.py
+++ b/qutebrowser/test/utils/test_debug.py
@@ -137,13 +137,13 @@ class TestDebug(unittest.TestCase):
def test_dbg_signal_eliding(self):
"""Test eliding in dbg_signal()."""
self.assertEqual(debug.dbg_signal(self.signal,
- [12345678901234567890123]),
- 'fake(1234567890123456789\u2026)')
+ ['x' * 201]),
+ "fake('{}\u2026)".format('x' * 198))
def test_dbg_signal_newline(self):
"""Test dbg_signal() with a newline."""
self.assertEqual(debug.dbg_signal(self.signal, ['foo\nbar']),
- 'fake(foo bar)')
+ r"fake('foo\nbar')")
if __name__ == '__main__':
diff --git a/qutebrowser/test/utils/test_utils.py b/qutebrowser/test/utils/test_utils.py
index 38ad74468..f7d915000 100644
--- a/qutebrowser/test/utils/test_utils.py
+++ b/qutebrowser/test/utils/test_utils.py
@@ -21,6 +21,7 @@
import os
import sys
+import enum
import shutil
import unittest
import os.path
@@ -511,5 +512,25 @@ class NormalizeTests(unittest.TestCase):
self.assertEqual(utils.normalize_keystr(orig), repl)
+class IsEnumTests(unittest.TestCase):
+
+ """Test is_enum."""
+
+ def test_enum(self):
+ """Test is_enum with an enum."""
+ e = enum.Enum('Foo', 'bar, baz')
+ self.assertTrue(utils.is_enum(e))
+
+ def test_class(self):
+ """Test is_enum with a non-enum class."""
+ # pylint: disable=multiple-statements,missing-docstring
+ class Test: pass
+ self.assertFalse(utils.is_enum(Test))
+
+ def test_object(self):
+ """Test is_enum with a non-enum object."""
+ self.assertFalse(utils.is_enum(23))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py
index 1716604f8..db0f6d323 100644
--- a/qutebrowser/utils/debug.py
+++ b/qutebrowser/utils/debug.py
@@ -21,58 +21,11 @@
import re
import sys
-import types
import functools
from PyQt5.QtCore import QEvent, QCoreApplication
from qutebrowser.utils import log, utils
-from qutebrowser.commands import cmdutils
-from qutebrowser.config import config, style
-
-
-@cmdutils.register(debug=True)
-def debug_crash(typ : ('exception', 'segfault') = 'exception'):
- """Crash for debugging purposes.
-
- Args:
- typ: either 'exception' or 'segfault'.
-
- Raises:
- raises Exception when typ is not segfault.
- segfaults when typ is (you don't say...)
- """
- if typ == 'segfault':
- # From python's Lib/test/crashers/bogus_code_obj.py
- co = types.CodeType(0, 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (),
- '', '', 1, b'')
- exec(co) # pylint: disable=exec-used
- raise Exception("Segfault failed (wat.)")
- else:
- raise Exception("Forced crash")
-
-
-@cmdutils.register(debug=True)
-def debug_all_widgets():
- """Print a list of all widgets to debug log."""
- s = QCoreApplication.instance().get_all_widgets()
- log.misc.debug(s)
-
-
-@cmdutils.register(debug=True)
-def debug_all_objects():
- """Print a list of all objects to the debug log."""
- s = QCoreApplication.instance().get_all_objects()
- log.misc.debug(s)
-
-
-@cmdutils.register(debug=True)
-def debug_cache_stats():
- """Print LRU cache stats."""
- config_info = config.instance().get.cache_info()
- style_info = style.get_stylesheet.cache_info()
- log.misc.debug('config: {}'.format(config_info))
- log.misc.debug('style: {}'.format(style_info))
def log_events(klass):
@@ -211,12 +164,12 @@ def signal_name(sig):
def _format_args(args=None, kwargs=None):
"""Format a list of arguments/kwargs to a function-call like string."""
if args is not None:
- arglist = [utils.compact_text(repr(arg), 50) for arg in args]
+ arglist = [utils.compact_text(repr(arg), 200) for arg in args]
else:
arglist = []
if kwargs is not None:
for k, v in kwargs.items():
- arglist.append('{}={}'.format(k, utils.compact_text(repr(v), 50)))
+ arglist.append('{}={}'.format(k, utils.compact_text(repr(v), 200)))
return ', '.join(arglist)
diff --git a/qutebrowser/utils/utilcmds.py b/qutebrowser/utils/utilcmds.py
index 933c14ad9..4fd1435ce 100644
--- a/qutebrowser/utils/utilcmds.py
+++ b/qutebrowser/utils/utilcmds.py
@@ -19,10 +19,11 @@
"""Misc. utility commands exposed to the user."""
+import types
+import functools
-from PyQt5.QtCore import pyqtRemoveInputHook, QCoreApplication
-from functools import partial
+from PyQt5.QtCore import QCoreApplication
from qutebrowser.utils import usertypes, log
from qutebrowser.commands import runners, cmdexc, cmdutils
@@ -40,7 +41,7 @@ def init():
@cmdutils.register()
-def later(ms : int, *command : {'nargs': '+'}):
+def later(ms: int, *command: {'nargs': '+'}):
"""Execute a command after some time.
Args:
@@ -58,31 +59,14 @@ def later(ms : int, *command : {'nargs': '+'}):
"int representation.")
_timers.append(timer)
cmdline = ' '.join(command)
- timer.timeout.connect(partial(_commandrunner.run_safely, cmdline))
+ timer.timeout.connect(functools.partial(
+ _commandrunner.run_safely, cmdline))
timer.timeout.connect(lambda: _timers.remove(timer))
timer.start()
-@cmdutils.register(debug=True, name='debug-set-trace')
-def set_trace():
- """Break into the debugger in the shell.
-
- //
-
- Based on http://stackoverflow.com/a/1745965/2085149
- """
- if sys.stdout is not None:
- sys.stdout.flush()
- print()
- print("When done debugging, remember to execute:")
- print(" from PyQt5 import QtCore; QtCore.pyqtRestoreInputHook()")
- print("before executing c(ontinue).")
- pyqtRemoveInputHook()
- pdb.set_trace()
-
-
@cmdutils.register(debug=True)
-def debug_crash(typ : ('exception', 'segfault') = 'exception'):
+def debug_crash(typ: ('exception', 'segfault')='exception'):
"""Crash for debugging purposes.
Args:
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index c790e6410..35afbed19 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -22,6 +22,7 @@
import os
import io
import sys
+import enum
import shlex
import os.path
import urllib.request
@@ -494,3 +495,11 @@ def disabled_excepthook():
# unchanged. Otherwise, we reset it.
if sys.excepthook is sys.__excepthook__:
sys.excepthook = old_excepthook
+
+
+def is_enum(obj):
+ """Check if a given object is an enum."""
+ try:
+ return issubclass(obj, enum.Enum)
+ except TypeError:
+ return False
diff --git a/qutebrowser/widgets/statusbar/command.py b/qutebrowser/widgets/statusbar/command.py
index d2d8363c7..3ff65ee64 100644
--- a/qutebrowser/widgets/statusbar/command.py
+++ b/qutebrowser/widgets/statusbar/command.py
@@ -19,7 +19,7 @@
"""The commandline in the statusbar."""
-from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication, QUrl
from PyQt5.QtWidgets import QSizePolicy, QApplication
from qutebrowser.keyinput import modeman, modeparsers
@@ -161,7 +161,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.show_cmd.emit()
@cmdutils.register(instance='mainwindow.status.cmd', name='set-cmd-text')
- def set_cmd_text_command(self, *strings):
+ def set_cmd_text_command(self, text):
"""Preset the statusbar to some text.
//
@@ -170,9 +170,16 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
strings which will get joined.
Args:
- strings: A list of strings to set.
+ text: The commandline to set.
"""
- text = ' '.join(strings)
+ app = QCoreApplication.instance()
+ url = app.mainwindow.tabs.current_url().toString(
+ QUrl.FullyEncoded | QUrl.RemovePassword)
+ # FIXME we currently replace the URL in any place in the arguments,
+ # rather than just replacing it if it is a dedicated argument. We could
+ # split the args, but then trailing spaces would be lost, so I'm not
+ # sure what's the best thing to do here
+ text = text.replace('{url}', url)
if not text[0] in modeparsers.STARTCHARS:
raise cmdexc.CommandError(
"Invalid command text '{}'.".format(text))
diff --git a/qutebrowser/widgets/tabbedbrowser.py b/qutebrowser/widgets/tabbedbrowser.py
index 43bc9ebd8..08d179d17 100644
--- a/qutebrowser/widgets/tabbedbrowser.py
+++ b/qutebrowser/widgets/tabbedbrowser.py
@@ -22,7 +22,7 @@
import functools
from PyQt5.QtWidgets import QSizePolicy
-from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize, QTimer
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize, QTimer, QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWebKitWidgets import QWebPage
@@ -205,7 +205,11 @@ class TabbedBrowser(tabwidget.TabWidget):
Raise:
CommandError if the current URL is invalid.
"""
- url = self.currentWidget().cur_url
+ widget = self.currentWidget()
+ if widget is None:
+ url = QUrl()
+ else:
+ url = widget.cur_url
try:
qtutils.ensure_valid(url)
except qtutils.QtValueError as e:
diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py
index b94d74759..34b8f3016 100644
--- a/qutebrowser/widgets/webview.py
+++ b/qutebrowser/widgets/webview.py
@@ -221,20 +221,20 @@ class WebView(QWebView):
e: The QMouseEvent.
"""
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
log.mouse.debug("Setting force target: {}".format(
- self._open_target))
+ self.open_target))
elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier):
if config.get('tabs', 'background-tabs'):
- self._open_target = usertypes.ClickTarget.tab_bg
+ self.open_target = usertypes.ClickTarget.tab_bg
else:
- self._open_target = usertypes.ClickTarget.tab
+ self.open_target = usertypes.ClickTarget.tab
log.mouse.debug("Middle click, setting target: {}".format(
- self._open_target))
+ self.open_target))
else:
- self._open_target = usertypes.ClickTarget.normal
+ self.open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target")
def shutdown(self):