From 57d51ad9bb88f05dced3c47a68688bfebc90e697 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Sep 2014 10:47:27 +0200 Subject: [PATCH] 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 --- .flake8 | 3 +- qutebrowser/browser/commands.py | 23 ++--- qutebrowser/browser/hints.py | 22 +++-- qutebrowser/commands/argparser.py | 50 ++++++++++- qutebrowser/commands/cmdexc.py | 7 ++ qutebrowser/commands/cmdutils.py | 110 +++++++++++++---------- qutebrowser/commands/command.py | 18 +++- qutebrowser/commands/runners.py | 36 +++++--- qutebrowser/config/configdata.py | 14 +-- qutebrowser/test/utils/test_debug.py | 6 +- qutebrowser/test/utils/test_utils.py | 21 +++++ qutebrowser/utils/debug.py | 51 +---------- qutebrowser/utils/utilcmds.py | 30 ++----- qutebrowser/utils/utils.py | 9 ++ qutebrowser/widgets/statusbar/command.py | 15 +++- qutebrowser/widgets/tabbedbrowser.py | 8 +- qutebrowser/widgets/webview.py | 12 +-- 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):