diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 5f0e9bf9c..399633257 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -480,8 +480,9 @@ For `typing.Union` types, the given `choices` are only checked if other types The following arguments are supported for `@cmdutils.argument`: - `flag`: Customize the short flag (`-x`) the argument will get. -- `win_id=True`: Mark the argument as special window ID argument. -- `count=True`: Mark the argument as special count argument. +- `value`: Tell qutebrowser to fill the argument with special values: + - `value=cmdutils.Value.count`: The `count` given by the user to the command. + - `value=cmdutils.Value.win_id`: The window ID of the current window. - `completion`: A completion function (see `qutebrowser.completions.models.*`) to use when completing arguments for the given command. - `choices`: The allowed string choices for the argument. diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py index 10acf9b4d..796c74eba 100644 --- a/qutebrowser/api/cmdutils.py +++ b/qutebrowser/api/cmdutils.py @@ -21,9 +21,12 @@ import inspect import typing +import enum from qutebrowser.utils import qtutils from qutebrowser.commands import command, cmdexc +# pylint: disable=unused-import +from qutebrowser.utils.usertypes import CommandValue as Value class CommandError(cmdexc.Error): diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 63c2da3b3..f4703850a 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -230,7 +230,7 @@ class CommandDispatcher: tabbar.setSelectionBehaviorOnRemove(old_selection_behavior) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_close(self, prev=False, next_=False, opposite=False, force=False, count=None): """Close the current/[count]th tab. @@ -253,7 +253,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', name='tab-pin') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_pin(self, count=None): """Pin/Unpin the current/[count]th tab. @@ -274,7 +274,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', name='open', maxsplit=0, scope='window') @cmdutils.argument('url', completion=urlmodel.url) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def openurl(self, url=None, related=False, bg=False, tab=False, window=False, count=None, secure=False, private=False): @@ -372,7 +372,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', name='reload', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def reloadpage(self, force=False, count=None): """Reload the current/[count]th tab. @@ -385,7 +385,7 @@ class CommandDispatcher: tab.reload(force=force) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def stop(self, count=None): """Stop loading in the current/[count]th tab. @@ -423,7 +423,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', name='print', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) @cmdutils.argument('pdf', flag='f', metavar='file') def printpage(self, preview=False, count=None, *, pdf=None): """Print the current/[count]th tab. @@ -514,7 +514,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('win_id', completion=miscmodels.window) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_give(self, win_id: int = None, keep: bool = False, count: int = None) -> None: """Give the current tab to a new or existing window if win_id given. @@ -575,7 +575,7 @@ class CommandDispatcher: raise cmdutils.CommandError(e) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def back(self, tab=False, bg=False, window=False, count=1): """Go back in the history of the current tab. @@ -588,7 +588,7 @@ class CommandDispatcher: self._back_forward(tab, bg, window, count, forward=False) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def forward(self, tab=False, bg=False, window=False, count=1): """Go forward in the history of the current tab. @@ -603,7 +603,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('where', choices=['prev', 'next', 'up', 'increment', 'decrement']) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def navigate(self, where: str, tab: bool = False, bg: bool = False, window: bool = False, count: int = 1) -> None: """Open typical prev/next links or navigate using the URL path. @@ -668,7 +668,7 @@ class CommandDispatcher: raise cmdutils.CommandError(e) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def scroll_px(self, dx: int, dy: int, count: int = 1) -> None: """Scroll the current tab by 'count * dx/dy' pixels. @@ -684,7 +684,7 @@ class CommandDispatcher: self._current_widget().scroller.delta(dx, dy) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def scroll(self, direction: str, count: int = 1) -> None: """Scroll the current tab in the given direction. @@ -721,7 +721,7 @@ class CommandDispatcher: func(count=count) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) @cmdutils.argument('horizontal', flag='x') def scroll_to_perc(self, perc: float = None, horizontal: bool = False, count: int = None) -> None: @@ -762,7 +762,7 @@ class CommandDispatcher: self._current_widget().scroller.to_anchor(name) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) @cmdutils.argument('top_navigate', metavar='ACTION', choices=('prev', 'decrement')) @cmdutils.argument('bottom_navigate', metavar='ACTION', @@ -891,7 +891,7 @@ class CommandDispatcher: maybe=True) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def zoom_in(self, count=1, quiet=False): """Increase the zoom level for the current tab. @@ -908,7 +908,7 @@ class CommandDispatcher: message.info("Zoom level: {}%".format(int(perc)), replace=True) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def zoom_out(self, count=1, quiet=False): """Decrease the zoom level for the current tab. @@ -925,7 +925,7 @@ class CommandDispatcher: message.info("Zoom level: {}%".format(int(perc)), replace=True) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def zoom(self, zoom=None, count=None, quiet=False): """Set the zoom level for the current tab. @@ -1005,7 +1005,7 @@ class CommandDispatcher: raise cmdutils.CommandError("Nothing to undo!") @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_prev(self, count=1): """Switch to the previous tab, or switch [count] tabs back. @@ -1025,7 +1025,7 @@ class CommandDispatcher: log.webview.debug("First tab") @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_next(self, count=1): """Switch to the next tab, or switch [count] tabs forward. @@ -1093,7 +1093,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) @cmdutils.argument('index', completion=miscmodels.buffer) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def buffer(self, index=None, count=None): """Select tab by index or url/title best match. @@ -1123,7 +1123,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['last']) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_focus(self, index: typing.Union[str, int] = None, count: int = None, no_last: bool = False) -> None: """Select the tab given as argument/[count]. @@ -1165,7 +1165,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['+', '-']) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_move(self, index: typing.Union[str, int] = None, count: int = None) -> None: """Move the current tab according to the argument and [count]. @@ -1212,7 +1212,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def spawn(self, cmdline, userscript=False, verbose=False, output=False, detach=False, count=None): """Spawn a command in a shell. @@ -1830,7 +1830,7 @@ class CommandDispatcher: tab.search.search(text, **options) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def search_next(self, count=1): """Continue the search to the ([count]th) next term. @@ -1864,7 +1864,7 @@ class CommandDispatcher: tab.search.next_result(result_cb=cb) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def search_prev(self, count=1): """Continue the search to the ([count]th) previous term. @@ -1899,7 +1899,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_next_line(self, count=1): """Move the cursor or selection to the next line. @@ -1910,7 +1910,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_prev_line(self, count=1): """Move the cursor or selection to the prev line. @@ -1921,7 +1921,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_next_char(self, count=1): """Move the cursor or selection to the next char. @@ -1932,7 +1932,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_prev_char(self, count=1): """Move the cursor or selection to the previous char. @@ -1943,7 +1943,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_end_of_word(self, count=1): """Move the cursor or selection to the end of the word. @@ -1954,7 +1954,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_next_word(self, count=1): """Move the cursor or selection to the next word. @@ -1965,7 +1965,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_prev_word(self, count=1): """Move the cursor or selection to the previous word. @@ -1988,7 +1988,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_start_of_next_block(self, count=1): """Move the cursor or selection to the start of next block. @@ -1999,7 +1999,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_start_of_prev_block(self, count=1): """Move the cursor or selection to the start of previous block. @@ -2010,7 +2010,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_end_of_next_block(self, count=1): """Move the cursor or selection to the end of next block. @@ -2021,7 +2021,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', modes=[KeyMode.caret], scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def move_to_end_of_prev_block(self, count=1): """Move the cursor or selection to the end of previous block. @@ -2056,7 +2056,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', debug=True) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def debug_webaction(self, action, count=1): """Execute a webaction. @@ -2256,7 +2256,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', name='tab-mute') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def tab_mute(self, count=None): """Mute/Unmute the current/[count]th tab. diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 6d9fa6c4e..b18e426d7 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1013,7 +1013,7 @@ class DownloadModel(QAbstractListModel): raise cmdutils.CommandError("There's no download {}!".format(count)) @cmdutils.register(instance='download-model', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def download_cancel(self, all_=False, count=0): """Cancel the last/[count]th download. @@ -1039,7 +1039,7 @@ class DownloadModel(QAbstractListModel): download.cancel() @cmdutils.register(instance='download-model', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def download_delete(self, count=0): """Delete the last/[count]th download from disk. @@ -1060,7 +1060,7 @@ class DownloadModel(QAbstractListModel): log.downloads.debug("deleted download {}".format(download)) @cmdutils.register(instance='download-model', scope='window', maxsplit=0) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def download_open(self, cmdline: str = None, count: int = 0) -> None: """Open the last/[count]th download. @@ -1086,7 +1086,7 @@ class DownloadModel(QAbstractListModel): download.open_file(cmdline) @cmdutils.register(instance='download-model', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def download_retry(self, count=0): """Retry the first failed/[count]th download. @@ -1121,7 +1121,7 @@ class DownloadModel(QAbstractListModel): download.remove() @cmdutils.register(instance='download-model', scope='window') - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def download_remove(self, all_=False, count=0): """Remove the last/[count]th download from the list. diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 811bc78eb..a318897d5 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -37,18 +37,13 @@ class ArgInfo: """Information about an argument.""" - win_id = attr.ib(False) - count = attr.ib(False) + value = attr.ib(None) hide = attr.ib(False) metavar = attr.ib(None) flag = attr.ib(None) completion = attr.ib(None) choices = attr.ib(None) - def __attrs_post_init__(self): - if self.win_id and self.count: - raise TypeError("Argument marked as both count/win_id!") - class Command: @@ -116,7 +111,6 @@ class Command: self.parser.add_argument('-h', '--help', action=argparser.HelpAction, default=argparser.SUPPRESS, nargs=0, help=argparser.SUPPRESS) - self._check_func() self.opt_args = collections.OrderedDict() self.namespace = None self._count = None @@ -130,6 +124,7 @@ class Command: self._qute_args = getattr(self.handler, 'qute_args', {}) self.handler.qute_args = None + self._check_func() self._inspect_func() def _check_prerequisites(self, win_id): @@ -154,9 +149,14 @@ class Command: def _check_func(self): """Make sure the function parameters don't violate any rules.""" signature = inspect.signature(self.handler) - if 'self' in signature.parameters and self._instance is None: - raise TypeError("{} is a class method, but instance was not " - "given!".format(self.name[0])) + if 'self' in signature.parameters: + if self._instance is None: + raise TypeError("{} is a class method, but instance was not " + "given!".format(self.name[0])) + arg_info = self.get_arg_info(signature.parameters['self']) + if arg_info.value is not None: + raise TypeError("{}: Can't fill 'self' with value!" + .format(self.name)) elif 'self' not in signature.parameters and self._instance is not None: raise TypeError("{} is not a class method, but instance was " "given!".format(self.name[0])) @@ -186,13 +186,18 @@ class Command: True if the parameter is special, False otherwise. """ arg_info = self.get_arg_info(param) - if arg_info.count: + if arg_info.value is None: + return False + elif arg_info.value == usertypes.CommandValue.count: if param.default is inspect.Parameter.empty: raise TypeError("{}: handler has count parameter " "without default!".format(self.name)) return True - elif arg_info.win_id: + elif arg_info.value == usertypes.CommandValue.win_id: return True + else: + raise TypeError("{}: Invalid value={!r} for argument '{}'!" + .format(self.name, arg_info.value, param.name)) return False def _inspect_func(self): @@ -325,9 +330,8 @@ class Command: return param.annotation elif param.default not in [None, inspect.Parameter.empty]: return type(param.default) - elif arginfo.count or arginfo.win_id or param.kind in [ - inspect.Parameter.VAR_POSITIONAL, - inspect.Parameter.VAR_KEYWORD]: + elif arginfo.value or param.kind in [inspect.Parameter.VAR_POSITIONAL, + inspect.Parameter.VAR_KEYWORD]: return None else: return str @@ -447,15 +451,13 @@ class Command: # Special case for 'self'. self._get_self_arg(win_id, param, args) continue - elif arg_info.count: - # Special case for count parameter. + elif arg_info.value == usertypes.CommandValue.count: self._get_count_arg(param, args, kwargs) continue - # elif arg_info.win_id: - elif arg_info.win_id: - # Special case for win_id parameter. + elif arg_info.value == usertypes.CommandValue.win_id: self._get_win_id_arg(win_id, param, args, kwargs) continue + value = self._get_param_value(param) if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: args.append(value) diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index 74a381507..574bc06af 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -79,7 +79,7 @@ class ConfigCommands: @cmdutils.register(instance='config-commands') @cmdutils.argument('option', completion=configmodel.option) @cmdutils.argument('value', completion=configmodel.value) - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('pattern', flag='u') def set(self, win_id, option=None, value=None, temp=False, print_=False, *, pattern=None): @@ -127,7 +127,7 @@ class ConfigCommands: @cmdutils.register(instance='config-commands', maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('command', completion=configmodel.bind) - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def bind(self, win_id, key=None, command=None, *, mode='normal', default=False): """Bind a key to a command. diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py index bd17f5664..5bf1ab18b 100644 --- a/qutebrowser/keyinput/macros.py +++ b/qutebrowser/keyinput/macros.py @@ -45,7 +45,7 @@ class MacroRecorder: self._last_register = None @cmdutils.register(instance='macro-recorder', name='record-macro') - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def record_macro_command(self, win_id, register=None): """Start or stop recording a macro. @@ -70,8 +70,8 @@ class MacroRecorder: self._recording_macro = register @cmdutils.register(instance='macro-recorder', name='run-macro') - @cmdutils.argument('win_id', win_id=True) - @cmdutils.argument('count', count=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) + @cmdutils.argument('count', value=cmdutils.Value.count) def run_macro_command(self, win_id, count=1, register=None): """Run a recorded macro. diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index de42cda4e..1661d2362 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -115,7 +115,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): @cmdutils.register(instance='status-command', name='set-cmd-text', scope='window', maxsplit=0) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def set_cmd_text_command(self, text, count=None, space=False, append=False, run_on_count=False): """Preset the statusbar to some text. diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 357d5be64..2a557ef50 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -508,7 +508,7 @@ class SessionManager(QObject): @cmdutils.register(instance='session-manager') @cmdutils.argument('name', completion=miscmodels.session) - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('with_private', flag='p') def session_save(self, name: typing.Union[str, Sentinel] = default, current: bool = False, diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 761498e5f..287704551 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -44,7 +44,7 @@ from qutebrowser.qt import sip @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) -@cmdutils.argument('win_id', win_id=True) +@cmdutils.argument('win_id', value=cmdutils.Value.win_id) def later(ms: int, command: str, win_id: int) -> None: """Execute a command after some time. @@ -74,8 +74,8 @@ def later(ms: int, command: str, win_id: int) -> None: @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) -@cmdutils.argument('win_id', win_id=True) -@cmdutils.argument('count', count=True) +@cmdutils.argument('win_id', value=cmdutils.Value.win_id) +@cmdutils.argument('count', value=cmdutils.Value.count) def repeat(times: int, command: str, win_id: int, count: int = None) -> None: """Repeat a given command. @@ -95,8 +95,8 @@ def repeat(times: int, command: str, win_id: int, count: int = None) -> None: @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) -@cmdutils.argument('win_id', win_id=True) -@cmdutils.argument('count', count=True) +@cmdutils.argument('win_id', value=cmdutils.Value.win_id) +@cmdutils.argument('count', value=cmdutils.Value.count) def run_with_count(count_arg: int, command: str, win_id: int, count: int = 1) -> None: """Run a command with the given count. @@ -122,7 +122,7 @@ def message_error(text): @cmdutils.register() -@cmdutils.argument('count', count=True) +@cmdutils.argument('count', value=cmdutils.Value.count) def message_info(text, count=1): """Show an info message in the statusbar. @@ -288,8 +288,8 @@ def debug_set_fake_clipboard(s=None): @cmdutils.register() -@cmdutils.argument('win_id', win_id=True) -@cmdutils.argument('count', count=True) +@cmdutils.argument('win_id', value=cmdutils.Value.win_id) +@cmdutils.argument('count', value=cmdutils.Value.count) def repeat_command(win_id, count=None): """Repeat the last executed command. @@ -358,7 +358,7 @@ def debug_log_filter(filters: str) -> None: @cmdutils.register() -@cmdutils.argument('current_win_id', win_id=True) +@cmdutils.argument('current_win_id', value=cmdutils.Value.win_id) def window_only(current_win_id): """Close all windows except for the current one.""" for win_id, window in objreg.window_registry.items(): @@ -377,7 +377,7 @@ def nop(): @cmdutils.register() -@cmdutils.argument('win_id', win_id=True) +@cmdutils.argument('win_id', value=cmdutils.Value.win_id) def version(win_id, paste=False): """Show version information. diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 5bb8d3aa9..d8f46ded8 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -256,6 +256,14 @@ MessageLevel = enum.Enum('MessageLevel', ['error', 'warning', 'info']) IgnoreCase = enum.Enum('IgnoreCase', ['smart', 'never', 'always']) +class CommandValue(enum.Enum): + + """Special values which are injected when running a command handler.""" + + count = 1 + win_id = 2 + + class Question(QObject): """A question asked to the user, e.g. via the status bar. diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py index f2318ab46..5e1389c80 100644 --- a/tests/unit/api/test_cmdutils.py +++ b/tests/unit/api/test_cmdutils.py @@ -190,23 +190,39 @@ class TestRegister: def test_win_id(self): @cmdutils.register() - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def fun(win_id): """Blah.""" assert objects.commands['fun']._get_call_args(42) == ([42], {}) def test_count(self): @cmdutils.register() - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def fun(count=0): """Blah.""" assert objects.commands['fun']._get_call_args(42) == ([0], {}) + def test_fill_self(self): + with pytest.raises(TypeError, match="fun: Can't fill 'self' with " + "value!"): + @cmdutils.register(instance='foobar') + @cmdutils.argument('self', value=cmdutils.Value.count) + def fun(self): + """Blah.""" + + def test_fill_invalid(self): + with pytest.raises(TypeError, match="fun: Invalid value='foo' for " + "argument 'arg'!"): + @cmdutils.register() + @cmdutils.argument('arg', value='foo') + def fun(arg): + """Blah.""" + def test_count_without_default(self): with pytest.raises(TypeError, match="fun: handler has count parameter " "without default!"): @cmdutils.register() - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def fun(count): """Blah.""" @@ -344,6 +360,17 @@ class TestArgument: } assert fun.qute_args == expected + def test_arginfo_boolean(self): + @cmdutils.argument('special1', value=cmdutils.Value.count) + @cmdutils.argument('special2', value=cmdutils.Value.win_id) + @cmdutils.argument('normal') + def fun(special1, special2, normal): + """Blah.""" + + assert fun.qute_args['special1'].value + assert fun.qute_args['special2'].value + assert not fun.qute_args['normal'].value + def test_wrong_order(self): """When @cmdutils.argument is used above (after) @register, fail.""" with pytest.raises(ValueError, match=r"@cmdutils.argument got called " @@ -353,13 +380,6 @@ class TestArgument: def fun(bar): """Blah.""" - def test_count_and_win_id_same_arg(self): - with pytest.raises(TypeError, - match="Argument marked as both count/win_id!"): - @cmdutils.argument('arg', count=True, win_id=True) - def fun(arg=0): - """Blah.""" - def test_no_docstring(self, caplog): with caplog.at_level(logging.WARNING): @cmdutils.register() diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 16ebb95dc..224268c90 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -112,12 +112,12 @@ def cmdutils_patch(monkeypatch, stubs, miscmodels_patch): """docstring.""" @cmdutils.argument('url', completion=miscmodels_patch.url) - @cmdutils.argument('count', count=True) + @cmdutils.argument('count', value=cmdutils.Value.count) def openurl(url=None, related=False, bg=False, tab=False, window=False, count=None): """docstring.""" - @cmdutils.argument('win_id', win_id=True) + @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('command', completion=miscmodels_patch.command) def bind(key, win_id, command=None, *, mode='normal'): """docstring."""