Merge remote-tracking branch 'origin/pr/5967' into dev
This commit is contained in:
commit
aefcb31da5
|
|
@ -0,0 +1,210 @@
|
|||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2021 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Module for parsing commands entered into the browser."""
|
||||
|
||||
import attr
|
||||
|
||||
from qutebrowser.commands import cmdexc, command
|
||||
from qutebrowser.misc import split, objects
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParseResult:
|
||||
|
||||
"""The result of parsing a commandline."""
|
||||
|
||||
cmd: Optional[command.Command]
|
||||
args: Optional[List[str]]
|
||||
cmdline: List[str]
|
||||
|
||||
|
||||
class CommandParser:
|
||||
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, partial_match=False):
|
||||
self._partial_match = partial_match
|
||||
|
||||
def _get_alias(self, text, aliases, default=None):
|
||||
"""Get an alias from the config.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
aliases: A map of aliases to commands.
|
||||
default : Default value to return when alias was not found.
|
||||
|
||||
Return:
|
||||
The new command string if an alias was found. Default value
|
||||
otherwise.
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
if parts[0] not in aliases:
|
||||
return default
|
||||
alias = aliases[parts[0]]
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
new_cmd = alias
|
||||
if text.endswith(' '):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def _parse_all_gen(self, text, *args, aliases=None, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
returns that.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
aliases: A map of aliases to commands.
|
||||
*args/**kwargs: Passed to parse().
|
||||
|
||||
Yields:
|
||||
ParseResult tuples.
|
||||
"""
|
||||
text = text.strip().lstrip(':').strip()
|
||||
if not text:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if aliases:
|
||||
text = self._get_alias(text, aliases, text)
|
||||
|
||||
if ';;' in text:
|
||||
# Get the first command and check if it doesn't want to have ;;
|
||||
# split.
|
||||
first = text.split(';;')[0]
|
||||
result = self.parse(first, *args, **kwargs)
|
||||
if result.cmd.no_cmd_split:
|
||||
sub_texts = [text]
|
||||
else:
|
||||
sub_texts = [e.strip() for e in text.split(';;')]
|
||||
else:
|
||||
sub_texts = [text]
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over _parse_all_gen."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False, best_match=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
fallback: Whether to do a fallback splitting when the command was
|
||||
unknown.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A ParseResult tuple.
|
||||
"""
|
||||
cmdstr, sep, argstr = text.partition(' ')
|
||||
|
||||
if not cmdstr and not fallback:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if self._partial_match:
|
||||
cmdstr = self._completion_match(cmdstr, best_match)
|
||||
|
||||
try:
|
||||
cmd = objects.commands[cmdstr]
|
||||
except KeyError:
|
||||
if not fallback:
|
||||
raise cmdexc.NoSuchCommandError(
|
||||
'{}: no such command'.format(cmdstr))
|
||||
cmdline = split.split(text, keep=keep)
|
||||
return ParseResult(cmd=None, args=None, cmdline=cmdline)
|
||||
|
||||
args = self._split_args(cmd, argstr, keep)
|
||||
if keep and args:
|
||||
cmdline = [cmdstr, sep + args[0]] + args[1:]
|
||||
elif keep:
|
||||
cmdline = [cmdstr, sep]
|
||||
else:
|
||||
cmdline = [cmdstr] + args[:]
|
||||
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
|
||||
|
||||
def _completion_match(self, cmdstr, best):
|
||||
"""Replace cmdstr with a matching completion if there's only one match.
|
||||
|
||||
Args:
|
||||
cmdstr: The string representing the entered command so far
|
||||
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = [cmd for cmd in sorted(objects.commands, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and best:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
"""Split the arguments from an arg string.
|
||||
|
||||
Args:
|
||||
cmd: The command we're currently handling.
|
||||
argstr: An argument string.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A list containing the split strings.
|
||||
"""
|
||||
if not argstr:
|
||||
return []
|
||||
elif cmd.maxsplit is None:
|
||||
return split.split(argstr, keep=keep)
|
||||
else:
|
||||
# If split=False, we still want to split the flags, but not
|
||||
# everything after that.
|
||||
# We first split the arg string and check the index of the first
|
||||
# non-flag args, then we re-split again properly.
|
||||
# example:
|
||||
#
|
||||
# input: "--foo -v bar baz"
|
||||
# first split: ['--foo', '-v', 'bar', 'baz']
|
||||
# 0 1 2 3
|
||||
# second split: ['--foo', '-v', 'bar baz']
|
||||
# (maxsplit=2)
|
||||
split_args = split.simple_split(argstr, keep=keep)
|
||||
flag_arg_count = 0
|
||||
for i, arg in enumerate(split_args):
|
||||
arg = arg.strip()
|
||||
if arg.startswith('-'):
|
||||
if arg in cmd.flags_with_args:
|
||||
flag_arg_count += 1
|
||||
else:
|
||||
maxsplit = i + cmd.maxsplit + flag_arg_count
|
||||
return split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
|
@ -30,9 +30,8 @@ from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
|||
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, command
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
|
||||
from qutebrowser.misc import split, objects
|
||||
from qutebrowser.keyinput import macros, modeman
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -43,16 +42,6 @@ _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str]
|
|||
last_command = {}
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParseResult:
|
||||
|
||||
"""The result of parsing a commandline."""
|
||||
|
||||
cmd: Optional[command.Command]
|
||||
args: Optional[List[str]]
|
||||
cmdline: List[str]
|
||||
|
||||
|
||||
def _url(tabbed_browser):
|
||||
"""Convenience method to get the current url."""
|
||||
try:
|
||||
|
|
@ -130,181 +119,6 @@ def replace_variables(win_id, arglist):
|
|||
return args
|
||||
|
||||
|
||||
class CommandParser:
|
||||
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, partial_match=False):
|
||||
self._partial_match = partial_match
|
||||
|
||||
def _get_alias(self, text, default=None):
|
||||
"""Get an alias from the config.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
default : Default value to return when alias was not found.
|
||||
|
||||
Return:
|
||||
The new command string if an alias was found. Default value
|
||||
otherwise.
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
aliases = config.cache['aliases']
|
||||
if parts[0] not in aliases:
|
||||
return default
|
||||
alias = aliases[parts[0]]
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
new_cmd = alias
|
||||
if text.endswith(' '):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def _parse_all_gen(self, text, *args, aliases=True, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
returns that.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
aliases: Whether to handle aliases.
|
||||
*args/**kwargs: Passed to parse().
|
||||
|
||||
Yields:
|
||||
ParseResult tuples.
|
||||
"""
|
||||
text = text.strip().lstrip(':').strip()
|
||||
if not text:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if aliases:
|
||||
text = self._get_alias(text, text)
|
||||
|
||||
if ';;' in text:
|
||||
# Get the first command and check if it doesn't want to have ;;
|
||||
# split.
|
||||
first = text.split(';;')[0]
|
||||
result = self.parse(first, *args, **kwargs)
|
||||
if result.cmd.no_cmd_split:
|
||||
sub_texts = [text]
|
||||
else:
|
||||
sub_texts = [e.strip() for e in text.split(';;')]
|
||||
else:
|
||||
sub_texts = [text]
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over _parse_all_gen."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
fallback: Whether to do a fallback splitting when the command was
|
||||
unknown.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A ParseResult tuple.
|
||||
"""
|
||||
cmdstr, sep, argstr = text.partition(' ')
|
||||
|
||||
if not cmdstr and not fallback:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if self._partial_match:
|
||||
cmdstr = self._completion_match(cmdstr)
|
||||
|
||||
try:
|
||||
cmd = objects.commands[cmdstr]
|
||||
except KeyError:
|
||||
if not fallback:
|
||||
raise cmdexc.NoSuchCommandError(
|
||||
'{}: no such command'.format(cmdstr))
|
||||
cmdline = split.split(text, keep=keep)
|
||||
return ParseResult(cmd=None, args=None, cmdline=cmdline)
|
||||
|
||||
args = self._split_args(cmd, argstr, keep)
|
||||
if keep and args:
|
||||
cmdline = [cmdstr, sep + args[0]] + args[1:]
|
||||
elif keep:
|
||||
cmdline = [cmdstr, sep]
|
||||
else:
|
||||
cmdline = [cmdstr] + args[:]
|
||||
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
|
||||
|
||||
def _completion_match(self, cmdstr):
|
||||
"""Replace cmdstr with a matching completion if there's only one match.
|
||||
|
||||
Args:
|
||||
cmdstr: The string representing the entered command so far
|
||||
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = [cmd for cmd in sorted(objects.commands, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and config.val.completion.use_best_match:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
"""Split the arguments from an arg string.
|
||||
|
||||
Args:
|
||||
cmd: The command we're currently handling.
|
||||
argstr: An argument string.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A list containing the split strings.
|
||||
"""
|
||||
if not argstr:
|
||||
return []
|
||||
elif cmd.maxsplit is None:
|
||||
return split.split(argstr, keep=keep)
|
||||
else:
|
||||
# If split=False, we still want to split the flags, but not
|
||||
# everything after that.
|
||||
# We first split the arg string and check the index of the first
|
||||
# non-flag args, then we re-split again properly.
|
||||
# example:
|
||||
#
|
||||
# input: "--foo -v bar baz"
|
||||
# first split: ['--foo', '-v', 'bar', 'baz']
|
||||
# 0 1 2 3
|
||||
# second split: ['--foo', '-v', 'bar baz']
|
||||
# (maxsplit=2)
|
||||
split_args = split.simple_split(argstr, keep=keep)
|
||||
flag_arg_count = 0
|
||||
for i, arg in enumerate(split_args):
|
||||
arg = arg.strip()
|
||||
if arg.startswith('-'):
|
||||
if arg in cmd.flags_with_args:
|
||||
flag_arg_count += 1
|
||||
else:
|
||||
maxsplit = i + cmd.maxsplit + flag_arg_count
|
||||
return split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
|
||||
class AbstractCommandRunner(QObject):
|
||||
|
||||
"""Abstract base class for CommandRunner."""
|
||||
|
|
@ -329,7 +143,7 @@ class CommandRunner(AbstractCommandRunner):
|
|||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self._parser = CommandParser(partial_match=partial_match)
|
||||
self._parser = parser.CommandParser(partial_match=partial_match)
|
||||
self._win_id = win_id
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -359,7 +173,8 @@ class CommandRunner(AbstractCommandRunner):
|
|||
|
||||
parsed = None
|
||||
with self._handle_error(safely):
|
||||
parsed = self._parser.parse_all(text)
|
||||
parsed = self._parser.parse_all(
|
||||
text, best_match=config.val.completion.use_best_match)
|
||||
|
||||
if parsed is None:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ from typing import TYPE_CHECKING
|
|||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import runners
|
||||
from qutebrowser.commands import parser
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import log, utils, debug, objreg
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
|
|
@ -139,8 +139,7 @@ class Completer(QObject):
|
|||
if not text or not text.strip():
|
||||
# Only ":", empty part under the cursor with nothing before/after
|
||||
return [], '', []
|
||||
parser = runners.CommandParser()
|
||||
result = parser.parse(text, fallback=True, keep=True)
|
||||
result = parser.CommandParser().parse(text, fallback=True, keep=True)
|
||||
parts = [x for x in result.cmdline if x]
|
||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from qutebrowser.config import configdata, configexc
|
||||
from qutebrowser.completion.models import completionmodel, listcategory, util
|
||||
from qutebrowser.commands import runners, cmdexc
|
||||
from qutebrowser.commands import parser, cmdexc
|
||||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
|
||||
|
|
@ -117,9 +117,8 @@ def _bind_current_default(key, info):
|
|||
|
||||
cmd_text = info.keyconf.get_command(seq, 'normal')
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
try:
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
cmd = parser.CommandParser().parse(cmd_text).cmd
|
||||
except cmdexc.NoSuchCommandError:
|
||||
data.append((cmd_text, '(Current) Invalid command!', key))
|
||||
else:
|
||||
|
|
@ -127,8 +126,7 @@ def _bind_current_default(key, info):
|
|||
|
||||
cmd_text = info.keyconf.get_command(seq, 'normal', default=True)
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
cmd = parser.CommandParser().parse(cmd_text).cmd
|
||||
data.append((cmd_text, '(Default) {}'.format(cmd.desc), key))
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping,
|
|||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
from qutebrowser.config import configdata, configexc, configutils
|
||||
from qutebrowser.utils import utils, log, urlmatch
|
||||
from qutebrowser.misc import objects
|
||||
|
|
@ -162,13 +163,39 @@ class KeyConfig:
|
|||
bindings[key] = binding
|
||||
return bindings
|
||||
|
||||
def _implied_cmd(self, cmdline: str) -> Optional[str]:
|
||||
"""Return cmdline, or the implied cmd if cmdline is a set-cmd-text."""
|
||||
try:
|
||||
results = parser.CommandParser().parse_all(
|
||||
cmdline, aliases=cache['aliases'])
|
||||
except cmdexc.NoSuchCommandError:
|
||||
return None
|
||||
|
||||
result = results[0]
|
||||
if result.cmd.name != "set-cmd-text":
|
||||
return cmdline
|
||||
*flags, cmd = result.args
|
||||
if "-a" in flags or "--append" in flags or not cmd.startswith(":"):
|
||||
return None # doesn't look like this sets a command
|
||||
return cmd.lstrip(":")
|
||||
|
||||
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
|
||||
"""Get a dict of commands to a list of bindings for the mode."""
|
||||
"""Get a dict of commands to a list of bindings for the mode.
|
||||
|
||||
This is intented for user-facing display of keybindings.
|
||||
As such, bindings for 'set-cmd-text [flags] :<cmd> ...' are translated
|
||||
to '<cmd> ...', as from the user's perspective these keys behave like
|
||||
bindings for '<cmd>' (that allow for further input before running).
|
||||
|
||||
See #5942.
|
||||
"""
|
||||
cmd_to_keys: KeyConfig._ReverseBindings = {}
|
||||
bindings = self.get_bindings_for(mode)
|
||||
for seq, full_cmd in sorted(bindings.items()):
|
||||
for cmd in full_cmd.split(';;'):
|
||||
cmd = cmd.strip()
|
||||
for cmdtext in full_cmd.split(';;'):
|
||||
cmd = self._implied_cmd(cmdtext.strip())
|
||||
if not cmd:
|
||||
continue
|
||||
cmd_to_keys.setdefault(cmd, [])
|
||||
# Put bindings involving modifiers last
|
||||
if any(info.modifiers for info in seq):
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ class NormalKeyParser(CommandKeyParser):
|
|||
_partial_timer: Timer to clear partial keypresses.
|
||||
"""
|
||||
|
||||
_sequence: keyutils.KeySequence
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
commandrunner: 'runners.CommandRunner',
|
||||
parent: QObject = None) -> None:
|
||||
|
|
@ -154,6 +156,8 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
|
|||
_last_press: The nature of the last keypress, a LastPress member.
|
||||
"""
|
||||
|
||||
_sequence: keyutils.KeySequence
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
commandrunner: 'runners.CommandRunner',
|
||||
hintmanager: hints.HintManager,
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for qutebrowser.commands.runners."""
|
||||
"""Tests for qutebrowser.commands.parser."""
|
||||
|
||||
import pytest
|
||||
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.commands import runners, cmdexc
|
||||
from qutebrowser.commands import parser, cmdexc
|
||||
|
||||
|
||||
class TestCommandParser:
|
||||
|
|
@ -35,26 +35,26 @@ class TestCommandParser:
|
|||
Args:
|
||||
cmdline_test: A pytest fixture which provides testcases.
|
||||
"""
|
||||
parser = runners.CommandParser()
|
||||
p = parser.CommandParser()
|
||||
if cmdline_test.valid:
|
||||
parser.parse_all(cmdline_test.cmd, aliases=False)
|
||||
p.parse_all(cmdline_test.cmd)
|
||||
else:
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse_all(cmdline_test.cmd, aliases=False)
|
||||
p.parse_all(cmdline_test.cmd)
|
||||
|
||||
def test_parse_all_with_alias(self, cmdline_test, monkeypatch,
|
||||
config_stub):
|
||||
if not cmdline_test.cmd:
|
||||
pytest.skip("Empty command")
|
||||
|
||||
config_stub.val.aliases = {'alias_name': cmdline_test.cmd}
|
||||
aliases = {'alias_name': cmdline_test.cmd}
|
||||
|
||||
parser = runners.CommandParser()
|
||||
p = parser.CommandParser()
|
||||
if cmdline_test.valid:
|
||||
assert len(parser.parse_all("alias_name")) > 0
|
||||
assert len(p.parse_all("alias_name", aliases=aliases)) > 0
|
||||
else:
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse_all("alias_name")
|
||||
p.parse_all("alias_name", aliases=aliases)
|
||||
|
||||
@pytest.mark.parametrize('command', ['', ' '])
|
||||
def test_parse_empty_with_alias(self, command):
|
||||
|
|
@ -63,9 +63,33 @@ class TestCommandParser:
|
|||
See https://github.com/qutebrowser/qutebrowser/issues/1690
|
||||
and https://github.com/qutebrowser/qutebrowser/issues/1773
|
||||
"""
|
||||
parser = runners.CommandParser()
|
||||
p = parser.CommandParser()
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse_all(command)
|
||||
p.parse_all(command, aliases={"foo": "bar"})
|
||||
|
||||
@pytest.mark.parametrize('command, name, args', [
|
||||
("set-cmd-text -s :open", "set-cmd-text", ["-s", ":open"]),
|
||||
("set-cmd-text :open {url:pretty}", "set-cmd-text",
|
||||
[":open {url:pretty}"]),
|
||||
("set-cmd-text -s :open -t", "set-cmd-text", ["-s", ":open -t"]),
|
||||
("set-cmd-text :open -t -r {url:pretty}", "set-cmd-text",
|
||||
[":open -t -r {url:pretty}"]),
|
||||
("set-cmd-text -s :open -b", "set-cmd-text", ["-s", ":open -b"]),
|
||||
("set-cmd-text :open -b -r {url:pretty}", "set-cmd-text",
|
||||
[":open -b -r {url:pretty}"]),
|
||||
("set-cmd-text -s :open -w", "set-cmd-text",
|
||||
["-s", ":open -w"]),
|
||||
("set-cmd-text :open -w {url:pretty}", "set-cmd-text",
|
||||
[":open -w {url:pretty}"]),
|
||||
("set-cmd-text /", "set-cmd-text", ["/"]),
|
||||
("set-cmd-text ?", "set-cmd-text", ["?"]),
|
||||
("set-cmd-text :", "set-cmd-text", [":"]),
|
||||
])
|
||||
def test_parse_result(self, command, name, args):
|
||||
p = parser.CommandParser()
|
||||
result = p.parse_all(command)[0]
|
||||
assert result.cmd.name == name
|
||||
assert result.args == args
|
||||
|
||||
|
||||
class TestCompletions:
|
||||
|
|
@ -86,8 +110,8 @@ class TestCompletions:
|
|||
|
||||
The same with it being disabled is tested by test_parse_all.
|
||||
"""
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
result = parser.parse('on')
|
||||
p = parser.CommandParser(partial_match=True)
|
||||
result = p.parse('on')
|
||||
assert result.cmd.name == 'one'
|
||||
|
||||
def test_dont_use_best_match(self, config_stub):
|
||||
|
|
@ -96,10 +120,10 @@ class TestCompletions:
|
|||
Should raise NoSuchCommandError
|
||||
"""
|
||||
config_stub.val.completion.use_best_match = False
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
p = parser.CommandParser(partial_match=True)
|
||||
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse('tw')
|
||||
p.parse('tw')
|
||||
|
||||
def test_use_best_match(self, config_stub):
|
||||
"""Test multiple completion options with use_best_match set to true.
|
||||
|
|
@ -107,7 +131,7 @@ class TestCompletions:
|
|||
The resulting command should be the best match
|
||||
"""
|
||||
config_stub.val.completion.use_best_match = True
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
p = parser.CommandParser(partial_match=True)
|
||||
|
||||
result = parser.parse('tw')
|
||||
result = p.parse('tw', best_match=True)
|
||||
assert result.cmd.name == 'two'
|
||||
|
|
@ -187,17 +187,39 @@ class TestKeyConfig:
|
|||
|
||||
@pytest.mark.parametrize('bindings, expected', [
|
||||
# Simple
|
||||
({'a': 'message-info foo', 'b': 'message-info bar'},
|
||||
{'message-info foo': ['a'], 'message-info bar': ['b']}),
|
||||
({'a': 'open foo', 'b': 'open bar'},
|
||||
{'open foo': ['a'], 'open bar': ['b']}),
|
||||
# Multiple bindings
|
||||
({'a': 'message-info foo', 'b': 'message-info foo'},
|
||||
{'message-info foo': ['b', 'a']}),
|
||||
({'a': 'open foo', 'b': 'open foo'},
|
||||
{'open foo': ['b', 'a']}),
|
||||
# With modifier keys (should be listed last and normalized)
|
||||
({'a': 'message-info foo', '<ctrl-a>': 'message-info foo'},
|
||||
{'message-info foo': ['a', '<Ctrl+a>']}),
|
||||
({'a': 'open foo', '<ctrl-a>': 'open foo'},
|
||||
{'open foo': ['a', '<Ctrl+a>']}),
|
||||
# Chained command
|
||||
({'a': 'message-info foo ;; message-info bar'},
|
||||
{'message-info foo': ['a'], 'message-info bar': ['a']}),
|
||||
({'a': 'open foo ;; open bar'},
|
||||
{'open foo': ['a'], 'open bar': ['a']}),
|
||||
# Command using set-cmd-text (#5942)
|
||||
(
|
||||
{
|
||||
"o": "set-cmd-text -s :open",
|
||||
"O": "set-cmd-text -s :open -t",
|
||||
"go": "set-cmd-text :open {url:pretty}",
|
||||
# all of these should be ignored
|
||||
"/": "set-cmd-text /",
|
||||
"?": "set-cmd-text ?",
|
||||
":": "set-cmd-text :",
|
||||
"a": "set-cmd-text no_leading_colon",
|
||||
"b": "set-cmd-text -s -a :skip_cuz_append",
|
||||
"c": "set-cmd-text --append :skip_cuz_append",
|
||||
},
|
||||
{
|
||||
"open": ["o"],
|
||||
"open -t": ["O"],
|
||||
"open {url:pretty}": ["go"],
|
||||
}
|
||||
),
|
||||
# Empty/unknown commands
|
||||
({"a": "", "b": "notreal"}, {}),
|
||||
])
|
||||
def test_get_reverse_bindings_for(self, key_config_stub, config_stub,
|
||||
no_bindings, bindings, expected):
|
||||
|
|
|
|||
Loading…
Reference in New Issue