diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 3ad152b21..1e592cb61 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -463,13 +463,16 @@ If the option name ends with '?', the value of the option is shown instead. If t [[set-cmd-text]] === set-cmd-text -Syntax: +:set-cmd-text 'text'+ +Syntax: +:set-cmd-text [*--space*] 'text'+ Preset the statusbar to some text. ==== positional arguments * +'text'+: The commandline to set. +==== optional arguments +* +*-s*+, +*--space*+: If given, a space is added to the end. + [[spawn]] === spawn Syntax: +:spawn [*--userscript*] 'args' ['args' ...]+ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 02d9bb5dc..56cab15bb 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1332,7 +1332,7 @@ Default: +pass:[\bprev(ious)?\b,\bback\b,\bolder\b,\b[<←≪]\b,\b(<<| == searchengines Definitions of search engines which can be used via the address bar. -The searchengine named `DEFAULT` is used when `general -> auto-search` is true and something else than a URL was entered to be opened. Other search engines can be used via the bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs. +The searchengine named `DEFAULT` is used when `general -> auto-search` is true and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs. == aliases Aliases for commands. diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index ae5ad1ded..478bf87df 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -19,8 +19,6 @@ """Module containing command managers (SearchRunner and CommandRunner).""" -import re - from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl from PyQt5.QtWebKitWidgets import QWebPage @@ -266,16 +264,8 @@ class CommandRunner(QObject): else: self._args = [] maxsplit = i + self._cmd.maxsplit + flag_arg_count - args = split.simple_split(argstr, keep=keep, - maxsplit=maxsplit) - for s in args: - # remove quotes and replace \" by " - if s == '""' or s == "''": - s = '' - else: - s = re.sub(r"""(^|[^\\])["']""", r'\1', s) - s = re.sub(r"""\\(["'])""", r'\1', s) - self._args.append(s) + self._args = split.simple_split(argstr, keep=keep, + maxsplit=maxsplit) break else: # If there are only flags, we got it right on the first try diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 368026aba..169465cd3 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -196,7 +196,13 @@ class Completer(QObject): data = model.data(indexes[0]) if data is None: return - data = self._quote(data) + parts = self.split() + try: + needs_quoting = cmdutils.cmd_dict[parts[0]].maxsplit is None + except KeyError: + needs_quoting = True + if needs_quoting: + data = self._quote(data) if model.count() == 1 and config.get('completion', 'quick-complete'): # If we only have one item, we want to apply it immediately # and go on to the next part. diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 8fede0160..d7716db97 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -171,8 +171,9 @@ def _init_key_config(): save_manager = objreg.get('save-manager') filename = os.path.join(standarddir.config(), 'keys.conf') save_manager.add_saveable( - 'key-config', key_config.save, key_config.changed, - config_opt=('general', 'auto-save-config'), filename=filename) + 'key-config', key_config.save, key_config.config_dirty, + config_opt=('general', 'auto-save-config'), filename=filename, + dirty=key_config.is_dirty) def _init_misc(): diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index d4580737c..fea34ab91 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -80,10 +80,10 @@ SECTION_DESC = { "bar.\n" "The searchengine named `DEFAULT` is used when " "`general -> auto-search` is true and something else than a URL was " - "entered to be opened. Other search engines can be used via the " - "bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will " - "be replaced by the search term, use `{{` and `}}` for literal " - "`{`/`}` signs."), + "entered to be opened. Other search engines can be used by prepending " + "the search engine name to the search term, e.g. " + "`:open google qutebrowser`. The string `{}` will be replaced by the " + "search term, use `{{` and `}}` for literal `{`/`}` signs."), 'aliases': ( "Aliases for commands.\n" "By default, no aliases are defined. Example which adds a new command " @@ -1029,14 +1029,14 @@ KEY_DATA = collections.OrderedDict([ ('normal', collections.OrderedDict([ ('search ""', ['']), - ('set-cmd-text ":open "', ['o']), - ('set-cmd-text ":open {url}"', ['go']), - ('set-cmd-text ":open -t "', ['O']), - ('set-cmd-text ":open -t {url}"', ['gO']), - ('set-cmd-text ":open -b "', ['xo']), - ('set-cmd-text ":open -b {url}"', ['xO']), - ('set-cmd-text ":open -w "', ['wo']), - ('set-cmd-text ":open -w {url}"', ['wO']), + ('set-cmd-text -s :open', ['o']), + ('set-cmd-text :open {url}', ['go']), + ('set-cmd-text -s :open -t', ['O']), + ('set-cmd-text :open -t {url}', ['gO']), + ('set-cmd-text -s :open -b', ['xo']), + ('set-cmd-text :open -b {url}', ['xO']), + ('set-cmd-text -s :open -w', ['wo']), + ('set-cmd-text :open -w {url}', ['wO']), ('open -t', ['ga']), ('tab-close', ['d', '']), ('tab-close -o', ['D']), @@ -1181,13 +1181,14 @@ KEY_DATA = collections.OrderedDict([ ]) -# A dict of {old_cmd: new_cmd} strings. +# A list of (regex, replacement) tuples of changed key commands. -CHANGED_KEY_COMMNADS = { - 'open -t about:blank': 'open -t', - 'open -b about:blank': 'open -b', - 'open -w about:blank': 'open -w', - 'download-page': 'download', - 'cancel-download': 'download-cancel', - 'search ""': 'search', -} +CHANGED_KEY_COMMANDS = [ + (re.compile(r'^open -([twb]) about:blank$'), r'open -\1'), + (re.compile(r'^download-page$'), r'download'), + (re.compile(r'^cancel-download$'), r'download-cancel'), + (re.compile(r'^search ""$'), r'search'), + (re.compile(r"^search ''$"), r'search'), + (re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'), + (re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'), +] diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index 006923bd8..5487afb11 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -55,13 +55,16 @@ class KeyConfigParser(QObject): _configfile: The filename of the config or None. _cur_section: The section currently being processed by _read(). _cur_command: The command currently being processed by _read(). + is_dirty: Whether the config is currently dirty. Signals: - changed: Emitted when the config has changed. + changed: Emitted when the internal data has changed. arg: Name of the mode which was changed. + config_dirty: Emitted when the config should be re-saved. """ changed = pyqtSignal(str) + config_dirty = pyqtSignal() def __init__(self, configdir, fname, parent=None): """Constructor. @@ -71,6 +74,7 @@ class KeyConfigParser(QObject): fname: The filename of the config. """ super().__init__(parent) + self.is_dirty = False self._cur_section = None self._cur_command = None # Mapping of section name(s) to key binding -> command dicts. @@ -165,6 +169,7 @@ class KeyConfigParser(QObject): raise cmdexc.CommandError(e) for m in mode.split(','): self.changed.emit(m) + self._mark_config_dirty() @cmdutils.register(instance='key-config') def unbind(self, key, mode=None): @@ -194,6 +199,7 @@ class KeyConfigParser(QObject): else: for m in mode.split(','): self.changed.emit(m) + self._mark_config_dirty() def _normalize_sectname(self, s): """Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'.""" @@ -246,6 +252,11 @@ class KeyConfigParser(QObject): for sectname in self.keybindings: self.changed.emit(sectname) + def _mark_config_dirty(self): + """Mark the config as dirty.""" + self.is_dirty = True + self.config_dirty.emit() + def _read_command(self, line): """Read a command from a line.""" if self._cur_section is None: @@ -255,7 +266,12 @@ class KeyConfigParser(QObject): command = line.split(maxsplit=1)[0] if command not in cmdutils.cmd_dict: raise KeyConfigError("Invalid command '{}'!".format(command)) - self._cur_command = configdata.CHANGED_KEY_COMMNADS.get(line, line) + for rgx, repl in configdata.CHANGED_KEY_COMMANDS: + if rgx.match(line): + line = rgx.sub(repl, line) + self._mark_config_dirty() + break + self._cur_command = line def _read_keybinding(self, line): """Read a key binding from a line.""" diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 38457b08b..48a106793 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -98,7 +98,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): @cmdutils.register(instance='status-command', name='set-cmd-text', scope='window', maxsplit=0) - def set_cmd_text_command(self, text): + def set_cmd_text_command(self, text, space=False): """Preset the statusbar to some text. // @@ -108,6 +108,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): Args: text: The commandline to set. + space: If given, a space is added to the end. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) @@ -127,6 +128,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): # I'm not sure what's the best thing to do here # https://github.com/The-Compiler/qutebrowser/issues/123 text = text.replace('{url}', url) + if space: + text += ' ' if not text or text[0] not in modeparsers.STARTCHARS: raise cmdexc.CommandError( "Invalid command text '{}'.".format(text)) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 9d4303735..df44ebbba 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -93,7 +93,7 @@ class TabWidget(QTabWidget): def set_page_title(self, idx, title): """Set the tab title user data.""" - self.tabBar().set_tab_data(idx, 'page-title', title.replace('&', '&&')) + self.tabBar().set_tab_data(idx, 'page-title', title) self.update_tab_title(idx) def page_title(self, idx): @@ -103,7 +103,7 @@ class TabWidget(QTabWidget): def update_tab_title(self, idx): """Update the tab text for the given tab.""" widget = self.widget(idx) - page_title = self.page_title(idx) + page_title = self.page_title(idx).replace('&', '&&') fields = {} if widget.load_status == webview.LoadStatus.loading: diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index f55387542..3a7772b1c 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -22,7 +22,7 @@ import os.path import collections -from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtCore import pyqtSlot, QObject, QTimer from qutebrowser.config import config from qutebrowser.commands import cmdutils @@ -139,7 +139,7 @@ class SaveManager(QObject): self._save_timer.start() def add_saveable(self, name, save, changed=None, config_opt=None, - filename=None): + filename=None, dirty=False): """Add a new saveable. Args: @@ -150,11 +150,15 @@ class SaveManager(QObject): or not. filename: The filename of the underlying file, so we can force saving if it doesn't exist. + dirty: Whether the saveable is already dirty. """ if name in self.saveables: raise ValueError("Saveable {} already registered!".format(name)) - self.saveables[name] = Saveable(name, save, changed, config_opt, - filename) + saveable = Saveable(name, save, changed, config_opt, filename) + self.saveables[name] = saveable + if dirty: + saveable.mark_dirty() + QTimer.singleShot(0, saveable.save) def save(self, name, is_exit=False, explicit=False, silent=False, force=False):