diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e03350dc4..beae58f87 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -60,7 +60,10 @@ Added Together with the previous setting, this should make wrapper scripts unnecessary. - Proxy authentication is now supported with the QtWebEngine backend. -- New `:config-cycle` command to cycle an option between multiple values. +- New config commands: + - `:config-cycle` to cycle an option between multiple values. + - `:config-unset` to remove a configured option + - `:config-clear` to remove all configured options Changed ~~~~~~~ diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 13ff65695..d7a6cc503 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -31,7 +31,9 @@ It is possible to run or bind multiple commands by separating them with `;;`. |<>|Load a bookmark. |<>|Select tab by index or url/title best match. |<>|Close the current window. +|<>|Set all settings back to their default. |<>|Cycle an option between multiple values. +|<>|Unset an option. |<>|Download a given URL, or current page if no URL given. |<>|Cancel the last/[count]th download. |<>|Remove all finished downloads from the list. @@ -202,6 +204,16 @@ The tab index to focus, starting with 1. === close Close the current window. +[[config-clear]] +=== config-clear +Syntax: +:config-clear [*--save*]+ + +Set all settings back to their default. + +==== optional arguments +* +*-s*+, +*--save*+: If given, all configuration in autoconfig.yml is also removed. + + [[config-cycle]] === config-cycle Syntax: +:config-cycle [*--temp*] [*--print*] 'option' 'values' ['values' ...]+ @@ -216,6 +228,20 @@ Cycle an option between multiple values. * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. * +*-p*+, +*--print*+: Print the value after setting. +[[config-unset]] +=== config-unset +Syntax: +:config-unset [*--temp*] 'option'+ + +Unset an option. + +This sets an option back to its default and removes it from autoconfig.yml. + +==== positional arguments +* +'option'+: The name of the option. + +==== optional arguments +* +*-t*+, +*--temp*+: Don't touch autoconfig.yml. + [[download]] === download Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+ diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 7107564af..7f2d1ab41 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -319,6 +319,32 @@ class Config(QObject): if save_yaml: self._yaml[name] = converted + def unset(self, name, *, save_yaml=False): + """Set the given setting back to its default.""" + self.get_opt(name) + try: + del self._values[name] + except KeyError: + return + self.changed.emit(name) + + if save_yaml: + self._yaml.unset(name) + + def clear(self, *, save_yaml=False): + """Clear all settings in the config. + + If save_yaml=True is given, also remove all customization from the YAML + file. + """ + old_values = self._values + self._values = {} + for name in old_values: + self.changed.emit(name) + + if save_yaml: + self._yaml.clear() + def update_mutables(self, *, save_yaml=False): """Update mutable settings if they changed. diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index 83e79f57d..1e9fe31cd 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -178,3 +178,28 @@ class ConfigCommands: if print_: self._print_value(option) + + @cmdutils.register(instance='config-commands') + @cmdutils.argument('option', completion=configmodel.option) + def config_unset(self, option, temp=False): + """Unset an option. + + This sets an option back to its default and removes it from + autoconfig.yml. + + Args: + option: The name of the option. + temp: Don't touch autoconfig.yml. + """ + with self._handle_config_error(): + self._config.unset(option, save_yaml=not temp) + + @cmdutils.register(instance='config-commands') + def config_clear(self, save=False): + """Set all settings back to their default. + + Args: + save: If given, all configuration in autoconfig.yml is also + removed. + """ + self._config.clear(save_yaml=save) diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 7eba211f6..840983480 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -102,9 +102,8 @@ class YamlConfig(QObject): return self._values[name] def __setitem__(self, name, value): - self.changed.emit() - self._dirty = True self._values[name] = value + self._mark_changed() def __contains__(self, name): return name in self._values @@ -112,6 +111,11 @@ class YamlConfig(QObject): def __iter__(self): return iter(self._values.items()) + def _mark_changed(self): + """Mark the YAML config as changed.""" + self._dirty = True + self.changed.emit() + def _save(self): """Save the settings to the YAML file if they've changed.""" if not self._dirty: @@ -167,6 +171,19 @@ class YamlConfig(QObject): self._values = global_obj self._dirty = False + def unset(self, name): + """Remove the given option name if it's configured.""" + try: + del self._values[name] + except KeyError: + return + self._mark_changed() + + def clear(self): + """Clear all values from the YAML file.""" + self._values = [] + self._mark_changed() + class ConfigAPI: diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 0bab0ce96..74407037a 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -432,6 +432,12 @@ class FakeYamlConfig: def __getitem__(self, key): return self._values[key] + def unset(self, name): + self._values.pop(name, None) + + def clear(self): + self._values = [] + class StatusBarCommandStub(QLineEdit): diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 24a0965d0..539b59dc2 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -279,6 +279,56 @@ class TestConfig: conf._set_value(opt, 'never') assert conf._values['tabs.show'] == 'never' + @pytest.mark.parametrize('save_yaml', [True, False]) + def test_unset(self, conf, qtbot, save_yaml): + name = 'tabs.show' + conf.set_obj(name, 'never', save_yaml=True) + assert conf.get(name) == 'never' + + with qtbot.wait_signal(conf.changed): + conf.unset(name, save_yaml=save_yaml) + + assert conf.get(name) == 'always' + if save_yaml: + assert name not in conf._yaml + else: + assert conf._yaml[name] == 'never' + + def test_unset_never_set(self, conf, qtbot): + name = 'tabs.show' + assert conf.get(name) == 'always' + + with qtbot.assert_not_emitted(conf.changed): + conf.unset(name) + + assert conf.get(name) == 'always' + + def test_unset_unknown(self, conf): + with pytest.raises(configexc.NoOptionError): + conf.unset('tabs') + + @pytest.mark.parametrize('save_yaml', [True, False]) + def test_clear(self, conf, qtbot, save_yaml): + name1 = 'tabs.show' + name2 = 'content.plugins' + conf.set_obj(name1, 'never', save_yaml=True) + conf.set_obj(name2, True, save_yaml=True) + assert conf._values[name1] == 'never' + assert conf._values[name2] is True + + with qtbot.waitSignals([conf.changed, conf.changed]) as blocker: + conf.clear(save_yaml=save_yaml) + + options = [e.args[0] for e in blocker.all_signals_and_args] + assert options == [name1, name2] + + if save_yaml: + assert name1 not in conf._yaml + assert name2 not in conf._yaml + else: + assert conf._yaml[name1] == 'never' + assert conf._yaml[name2] is True + def test_read_yaml(self, conf): assert not conf._yaml.loaded conf._yaml['content.plugins'] = True diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index a839810cc..85e4d0543 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -228,6 +228,41 @@ class TestCycle: assert msg.text == 'auto_save.session = true' +class TestUnsetAndClear: + + """Test :config-unset and :config-clear.""" + + @pytest.mark.parametrize('temp', [True, False]) + def test_unset(self, commands, config_stub, temp): + name = 'tabs.show' + config_stub.set_obj(name, 'never', save_yaml=True) + + commands.config_unset(name, temp=temp) + + assert config_stub.get(name) == 'always' + if temp: + assert config_stub._yaml[name] == 'never' + else: + assert name not in config_stub._yaml + + def test_unset_unknown_option(self, commands): + with pytest.raises(cmdexc.CommandError, match="No option 'tabs'"): + commands.config_unset('tabs') + + @pytest.mark.parametrize('save', [True, False]) + def test_clear(self, commands, config_stub, save): + name = 'tabs.show' + config_stub.set_obj(name, 'never', save_yaml=True) + + commands.config_clear(save=save) + + assert config_stub.get(name) == 'always' + if save: + assert name not in config_stub._yaml + else: + assert config_stub._yaml[name] == 'never' + + class TestBind: """Tests for :bind and :unbind.""" diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 6274b7d50..ecb1e763f 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -209,6 +209,31 @@ class TestYaml: assert isinstance(error.exception, OSError) assert error.traceback is None + def test_unset(self, qtbot, config_tmpdir): + name = 'tabs.show' + yaml = configfiles.YamlConfig() + yaml[name] = 'never' + + with qtbot.wait_signal(yaml.changed): + yaml.unset(name) + + assert name not in yaml + + def test_unset_never_set(self, qtbot, config_tmpdir): + yaml = configfiles.YamlConfig() + with qtbot.assert_not_emitted(yaml.changed): + yaml.unset('tabs.show') + + def test_clear(self, qtbot, config_tmpdir): + name = 'tabs.show' + yaml = configfiles.YamlConfig() + yaml[name] = 'never' + + with qtbot.wait_signal(yaml.changed): + yaml.clear() + + assert name not in yaml + class ConfPy: