Merge branch 'master' of github.com:The-Compiler/qutebrowser
This commit is contained in:
commit
fa2340b61e
@ -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:
|
||||||
cmdstr, sep, argstr = text.partition(' ')
|
cmd = None
|
||||||
return [cmdstr, sep] + argstr.split()
|
args = None
|
||||||
elif fallback:
|
if keep:
|
||||||
return text.split()
|
cmdstr, sep, argstr = text.partition(' ')
|
||||||
|
cmdline = [cmdstr, sep] + argstr.split()
|
||||||
|
else:
|
||||||
|
cmdline = text.split()
|
||||||
else:
|
else:
|
||||||
raise cmdexc.NoSuchCommandError(
|
raise cmdexc.NoSuchCommandError('{}: no such command'.format(
|
||||||
'{}: no such command'.format(cmdstr))
|
cmdstr))
|
||||||
self._split_args(argstr, keep)
|
|
||||||
retargs = self._args[:]
|
|
||||||
if keep and retargs:
|
|
||||||
return [cmdstr, sep + retargs[0]] + retargs[1:]
|
|
||||||
elif keep:
|
|
||||||
return [cmdstr, sep]
|
|
||||||
else:
|
else:
|
||||||
return [cmdstr] + retargs
|
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 _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)
|
else: # pylint: disable=useless-else-on-loop
|
||||||
break
|
|
||||||
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 ;;
|
if count is not None:
|
||||||
# split.
|
result.cmd.run(self._win_id, args, count=count)
|
||||||
if not self._cmd.no_cmd_split:
|
else:
|
||||||
for sub in text.split(';;'):
|
result.cmd.run(self._win_id, args)
|
||||||
self.run(sub, count)
|
|
||||||
return
|
|
||||||
args = replace_variables(self._win_id, self._args)
|
|
||||||
if count is not None:
|
|
||||||
self._cmd.run(self._win_id, args, count=count)
|
|
||||||
else:
|
|
||||||
self._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):
|
||||||
|
@ -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))
|
||||||
|
@ -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>']),
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user