Merge branch 'master' of github.com:The-Compiler/qutebrowser

This commit is contained in:
Florian Bruhin 2015-04-13 07:53:59 +02:00
commit fa2340b61e
4 changed files with 94 additions and 52 deletions

View File

@ -19,6 +19,8 @@
"""Module containing command managers (SearchRunner and CommandRunner).""" """Module containing command managers (SearchRunner and CommandRunner)."""
import collections
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
from PyQt5.QtWebKitWidgets import QWebPage from PyQt5.QtWebKitWidgets import QWebPage
@ -28,6 +30,9 @@ from qutebrowser.utils import message, log, utils, objreg, qtutils
from qutebrowser.misc import split from qutebrowser.misc import split
ParseResult = collections.namedtuple('ParseResult', 'cmd, args, cmdline')
def replace_variables(win_id, arglist): def replace_variables(win_id, arglist):
"""Utility function to replace variables like {url} in a list of args.""" """Utility function to replace variables like {url} in a list of args."""
args = [] args = []
@ -152,15 +157,11 @@ class CommandRunner(QObject):
"""Parse and run qutebrowser commandline commands. """Parse and run qutebrowser commandline commands.
Attributes: Attributes:
_cmd: The command which was parsed.
_args: The arguments which were parsed.
_win_id: The window this CommandRunner is associated with. _win_id: The window this CommandRunner is associated with.
""" """
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
super().__init__(parent) super().__init__(parent)
self._cmd = None
self._args = []
self._win_id = win_id self._win_id = win_id
def _get_alias(self, text): def _get_alias(self, text):
@ -186,7 +187,34 @@ class CommandRunner(QObject):
new_cmd += ' ' new_cmd += ' '
return new_cmd return new_cmd
def parse(self, text, aliases=True, fallback=False, keep=False): def parse_all(self, text, *args, **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.
*args/**kwargs: Passed to parse().
Yields:
ParseResult tuples.
"""
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(self, text, *, aliases=True, fallback=False, keep=False):
"""Split the commandline text into command and arguments. """Split the commandline text into command and arguments.
Args: Args:
@ -197,7 +225,7 @@ class CommandRunner(QObject):
keep: Whether to keep special chars and whitespace keep: Whether to keep special chars and whitespace
Return: Return:
A split string commandline, e.g ['open', 'www.google.com'] A (cmd, args, cmdline) ParseResult tuple.
""" """
cmdstr, sep, argstr = text.partition(' ') cmdstr, sep, argstr = text.partition(' ')
if not cmdstr and not fallback: if not cmdstr and not fallback:
@ -209,29 +237,34 @@ class CommandRunner(QObject):
return self.parse(new_cmd, aliases=False, fallback=fallback, return self.parse(new_cmd, aliases=False, fallback=fallback,
keep=keep) keep=keep)
try: try:
self._cmd = cmdutils.cmd_dict[cmdstr] cmd = cmdutils.cmd_dict[cmdstr]
except KeyError: except KeyError:
if fallback and keep: if fallback:
cmd = None
args = None
if keep:
cmdstr, sep, argstr = text.partition(' ') cmdstr, sep, argstr = text.partition(' ')
return [cmdstr, sep] + argstr.split() cmdline = [cmdstr, sep] + argstr.split()
elif fallback:
return text.split()
else: else:
raise cmdexc.NoSuchCommandError( cmdline = text.split()
'{}: no such command'.format(cmdstr)) else:
self._split_args(argstr, keep) raise cmdexc.NoSuchCommandError('{}: no such command'.format(
retargs = self._args[:] cmdstr))
if keep and retargs: else:
return [cmdstr, sep + retargs[0]] + retargs[1:] args = self._split_args(cmd, argstr, keep)
if keep and args:
cmdline = [cmdstr, sep + args[0]] + args[1:]
elif keep: elif keep:
return [cmdstr, sep] cmdline = [cmdstr, sep]
else: else:
return [cmdstr] + retargs cmdline = [cmdstr] + args[:]
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
def _split_args(self, argstr, keep): def _split_args(self, cmd, argstr, keep):
"""Split the arguments from an arg string. """Split the arguments from an arg string.
Args: Args:
cmd: The command we're currently handling.
argstr: An argument string. argstr: An argument string.
keep: Whether to keep special chars and whitespace keep: Whether to keep special chars and whitespace
@ -239,9 +272,9 @@ class CommandRunner(QObject):
A list containing the splitted strings. A list containing the splitted strings.
""" """
if not argstr: if not argstr:
self._args = [] return []
elif self._cmd.maxsplit is None: elif cmd.maxsplit is None:
self._args = split.split(argstr, keep=keep) return split.split(argstr, keep=keep)
else: else:
# If split=False, we still want to split the flags, but not # If split=False, we still want to split the flags, but not
# everything after that. # everything after that.
@ -259,18 +292,16 @@ class CommandRunner(QObject):
for i, arg in enumerate(split_args): for i, arg in enumerate(split_args):
arg = arg.strip() arg = arg.strip()
if arg.startswith('-'): if arg.startswith('-'):
if arg.lstrip('-') in self._cmd.flags_with_args: if arg.lstrip('-') in cmd.flags_with_args:
flag_arg_count += 1 flag_arg_count += 1
else: else:
self._args = [] maxsplit = i + cmd.maxsplit + flag_arg_count
maxsplit = i + self._cmd.maxsplit + flag_arg_count return split.simple_split(argstr, keep=keep,
self._args = split.simple_split(argstr, keep=keep,
maxsplit=maxsplit) maxsplit=maxsplit)
break else: # pylint: disable=useless-else-on-loop
else:
# If there are only flags, we got it right on the first try # If there are only flags, we got it right on the first try
# already. # already.
self._args = split_args return split_args
def run(self, text, count=None): def run(self, text, count=None):
"""Parse a command from a line of text and run it. """Parse a command from a line of text and run it.
@ -279,19 +310,12 @@ class CommandRunner(QObject):
text: The text to parse. text: The text to parse.
count: The count to pass to the command. count: The count to pass to the command.
""" """
self.parse(text) for result in self.parse_all(text):
if ';;' in text: args = replace_variables(self._win_id, result.args)
# Get the first command and check if it doesn't want to have ;;
# split.
if not self._cmd.no_cmd_split:
for sub in text.split(';;'):
self.run(sub, count)
return
args = replace_variables(self._win_id, self._args)
if count is not None: if count is not None:
self._cmd.run(self._win_id, args, count=count) result.cmd.run(self._win_id, args, count=count)
else: else:
self._cmd.run(self._win_id, args) result.cmd.run(self._win_id, args)
@pyqtSlot(str, int) @pyqtSlot(str, int)
def run_safely(self, text, count=None): def run_safely(self, text, count=None):

View File

@ -302,7 +302,8 @@ class Completer(QObject):
# the whitespace. # the whitespace.
return [text] return [text]
runner = runners.CommandRunner(self._win_id) runner = runners.CommandRunner(self._win_id)
parts = runner.parse(text, fallback=True, aliases=aliases, keep=keep) result = runner.parse(text, fallback=True, aliases=aliases, keep=keep)
parts = result.cmdline
if self._empty_item_idx is not None: if self._empty_item_idx is not None:
log.completion.debug("Empty element queued at {}, " log.completion.debug("Empty element queued at {}, "
"inserting.".format(self._empty_item_idx)) "inserting.".format(self._empty_item_idx))

View File

@ -1106,7 +1106,7 @@ KEY_DATA = collections.OrderedDict([
('set-cmd-text :open -b {url}', ['xO']), ('set-cmd-text :open -b {url}', ['xO']),
('set-cmd-text -s :open -w', ['wo']), ('set-cmd-text -s :open -w', ['wo']),
('set-cmd-text :open -w {url}', ['wO']), ('set-cmd-text :open -w {url}', ['wO']),
('open -t', ['ga']), ('open -t', ['ga', '<Ctrl-T>']),
('tab-close', ['d', '<Ctrl-W>']), ('tab-close', ['d', '<Ctrl-W>']),
('tab-close -o', ['D']), ('tab-close -o', ['D']),
('tab-only', ['co']), ('tab-only', ['co']),
@ -1189,7 +1189,6 @@ KEY_DATA = collections.OrderedDict([
('tab-focus last', ['<Ctrl-Tab>']), ('tab-focus last', ['<Ctrl-Tab>']),
('enter-mode passthrough', ['<Ctrl-V>']), ('enter-mode passthrough', ['<Ctrl-V>']),
('quit', ['<Ctrl-Q>']), ('quit', ['<Ctrl-Q>']),
('open -t', ['<Ctrl-T>']),
('scroll-page 0 1', ['<Ctrl-F>']), ('scroll-page 0 1', ['<Ctrl-F>']),
('scroll-page 0 -1', ['<Ctrl-B>']), ('scroll-page 0 -1', ['<Ctrl-B>']),
('scroll-page 0 0.5', ['<Ctrl-D>']), ('scroll-page 0 0.5', ['<Ctrl-D>']),

View File

@ -257,15 +257,33 @@ class KeyConfigParser(QObject):
self.is_dirty = True self.is_dirty = True
self.config_dirty.emit() self.config_dirty.emit()
def _validate_command(self, line):
"""Check if a given command is valid."""
commands = line.split(';;')
try:
first_cmd = commands[0].split(maxsplit=1)[0].strip()
cmd = cmdutils.cmd_dict[first_cmd]
if cmd.no_cmd_split:
commands = [line]
except (KeyError, IndexError):
pass
for cmd in commands:
if not cmd.strip():
raise KeyConfigError("Got empty command (line: {!r})!".format(
line))
commands = [c.split(maxsplit=1)[0].strip() for c in commands]
for cmd in commands:
if cmd not in cmdutils.cmd_dict:
raise KeyConfigError("Invalid command '{}'!".format(cmd))
def _read_command(self, line): def _read_command(self, line):
"""Read a command from a line.""" """Read a command from a line."""
if self._cur_section is None: if self._cur_section is None:
raise KeyConfigError("Got command '{}' without getting a " raise KeyConfigError("Got command '{}' without getting a "
"section!".format(line)) "section!".format(line))
else: else:
command = line.split(maxsplit=1)[0] self._validate_command(line)
if command not in cmdutils.cmd_dict:
raise KeyConfigError("Invalid command '{}'!".format(command))
for rgx, repl in configdata.CHANGED_KEY_COMMANDS: for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
if rgx.match(line): if rgx.match(line):
line = rgx.sub(repl, line) line = rgx.sub(repl, line)