From 34815f5cf815db7909252ec59e090f4dba57ee47 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Mar 2018 18:30:44 +0100 Subject: Make bindings.default only settable in autoconfig.yml Fixes #3131 --- doc/changelog.asciidoc | 2 ++ doc/help/settings.asciidoc | 2 ++ qutebrowser/completion/models/configmodel.py | 3 ++- qutebrowser/config/config.py | 13 +++++++++++-- qutebrowser/config/configdata.py | 4 +++- qutebrowser/config/configdata.yml | 1 + qutebrowser/config/configexc.py | 9 +++++++++ qutebrowser/config/configfiles.py | 6 ++++++ qutebrowser/html/settings.html | 2 +- scripts/dev/src2asciidoc.py | 2 ++ tests/unit/completion/test_models.py | 5 ++--- tests/unit/config/test_config.py | 28 ++++++++++++++++++++++++++++ tests/unit/config/test_configexc.py | 6 ++++++ tests/unit/config/test_configfiles.py | 10 ++++++++++ 14 files changed, 85 insertions(+), 8 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index dfc11fd59..c526e4430 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -71,6 +71,8 @@ Changed * Yes/no prompts don't use keybindings from the `prompt` section anymore, they have their own `yesno` section instead. * Trying to bind invalid keys now shows an error. + * The `bindings.default` setting can now only be set in a `config.py`, and + existing values in `autoconfig.yml` are ignored. - Improvements for GreaseMonkey support: * `@include` and `@exclude` now support regex matches. With QtWebEngine and Qt 5.8 and newer, Qt handles the matching, but similar functionality will be diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 0c2d09d5f..bfbb8672e 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -376,6 +376,8 @@ Default keybindings. If you want to add bindings, modify `bindings.commands` ins The main purpose of this setting is that you can set it to an empty dictionary if you want to load no default keybindings at all. If you want to preserve default bindings (and get new bindings when there is an update), use `config.bind()` in `config.py` or the `:bind` command, and leave this setting alone. +This setting can only be set in config.py. + Type: <> Default: diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 877de62b7..b462442a0 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -29,7 +29,8 @@ def option(*, info): """A CompletionModel filled with settings and their descriptions.""" model = completionmodel.CompletionModel(column_widths=(20, 70, 10)) options = ((opt.name, opt.description, info.config.get_str(opt.name)) - for opt in configdata.DATA.values()) + for opt in configdata.DATA.values() + if not opt.no_autoconfig) model.add_category(listcategory.ListCategory("Options", options)) return model diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index eb2a81594..de8cc7042 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -286,6 +286,11 @@ class Config(QObject): log.config.debug("Config option changed: {} = {}".format( opt.name, value)) + def _check_yaml(self, opt, save_yaml): + """Make sure the given option may be set in autoconfig.yml.""" + if save_yaml and opt.no_autoconfig: + raise configexc.NoAutoconfigError(opt.name) + def read_yaml(self): """Read the YAML settings from self._yaml.""" self._yaml.load() @@ -383,7 +388,9 @@ class Config(QObject): If save_yaml=True is given, store the new value to YAML. """ - self._set_value(self.get_opt(name), value, pattern=pattern) + opt = self.get_opt(name) + self._check_yaml(opt, save_yaml) + self._set_value(opt, value, pattern=pattern) if save_yaml: self._yaml.set_obj(name, value, pattern=pattern) @@ -393,6 +400,7 @@ class Config(QObject): If save_yaml=True is given, store the new value to YAML. """ opt = self.get_opt(name) + self._check_yaml(opt, save_yaml) converted = opt.typ.from_str(value) log.config.debug("Setting {} (type {}) to {!r} (converted from {!r})" .format(name, opt.typ.__class__.__name__, converted, @@ -403,7 +411,8 @@ class Config(QObject): def unset(self, name, *, save_yaml=False, pattern=None): """Set the given setting back to its default.""" - self.get_opt(name) # To check whether it exists + opt = self.get_opt(name) + self._check_yaml(opt, save_yaml) changed = self._values[name].remove(pattern) if changed: self.changed.emit(name) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 52ad123e1..c617fca14 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -50,6 +50,7 @@ class Option: description = attr.ib() supports_pattern = attr.ib(default=False) restart = attr.ib(default=False) + no_autoconfig = attr.ib(default=False) @attr.s @@ -199,7 +200,7 @@ def _read_yaml(yaml_data): data = utils.yaml_load(yaml_data) keys = {'type', 'default', 'desc', 'backend', 'restart', - 'supports_pattern'} + 'supports_pattern', 'no_autoconfig'} for name, option in data.items(): if set(option.keys()) == {'renamed'}: @@ -227,6 +228,7 @@ def _read_yaml(yaml_data): description=option['desc'], restart=option.get('restart', False), supports_pattern=option.get('supports_pattern', False), + no_autoconfig=option.get('no_autoconfig', False), ) # Make sure no key shadows another. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 28c3a5d87..92d100744 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -2177,6 +2177,7 @@ bindings.key_mappings: `bindings.commands`), the mapping is ignored. bindings.default: + no_autoconfig: true default: normal: : clear-keychain ;; search ;; fullscreen --leave diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index e08bec913..4d1ab5d7f 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -31,6 +31,15 @@ class Error(Exception): pass +class NoAutoconfigError(Error): + + """Raised when this option can't be set in autoconfig.yml.""" + + def __init__(self, name): + super().__init__("The {} setting can only be set in config.py!" + .format(name)) + + class BackendError(Error): """Raised when this setting is unavailable with the current backend.""" diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 05ed23e60..fdb1583e0 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -262,6 +262,12 @@ class YamlConfig(QObject): del settings[old] self._mark_changed() + # bindings.default can't be set in autoconfig.yml anymore, so ignore + # old values. + if 'bindings.default' in settings: + del settings['bindings.default'] + self._mark_changed() + return settings def _validate(self, settings): diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html index b370c0d91..62b424a59 100644 --- a/qutebrowser/html/settings.html +++ b/qutebrowser/html/settings.html @@ -33,7 +33,7 @@ input { width: 98%; } Setting Value - {% for option in configdata.DATA.values() %} + {% for option in configdata.DATA.values() if not option.no_autoconfig %} {{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }}) diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 5fab901fc..cc00c3757 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -421,6 +421,8 @@ def _generate_setting_option(f, opt): f.write("This setting requires a restart.\n") if opt.supports_pattern: f.write("\nThis setting supports URL patterns.\n") + if opt.no_autoconfig: + f.write("\nThis setting can only be set in config.py.\n") f.write("\n") typ = opt.typ.get_name().replace(',', ',') f.write('Type: <>\n'.format(typ=typ)) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 5240f2813..e14c5a466 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -111,7 +111,8 @@ def configdata_stub(config_stub, monkeypatch, configdata_init): ]) }, backends=[], - raw_backends=None)), + raw_backends=None, + no_autoconfig=True)), ('bindings.commands', configdata.Option( name='bindings.commands', description='Default keybindings', @@ -655,8 +656,6 @@ def test_setting_option_completion(qtmodeltester, config_stub, ('bindings.commands', 'Default keybindings', ( '{"normal": {"": "quit", "ZQ": "quit", ' '"I": "invalid", "d": "scroll down"}}')), - ('bindings.default', 'Default keybindings', - '{"normal": {"": "quit", "d": "tab-close"}}'), ('content.javascript.enabled', 'Enable/Disable JavaScript', 'true'), ] diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 40e82ba4b..d51d79370 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -621,6 +621,34 @@ class TestConfig: meth('content.cookies.accept', 'all') assert not conf._values['content.cookies.accept'] + @pytest.mark.parametrize('method, value', [ + ('set_obj', {}), + ('set_str', '{}'), + ]) + def test_set_no_autoconfig_save(self, conf, qtbot, yaml_value, + method, value): + meth = getattr(conf, method) + option = 'bindings.default' + with pytest.raises(configexc.NoAutoconfigError): + with qtbot.assert_not_emitted(conf.changed): + meth(option, value, save_yaml=True) + + assert not conf._values[option] + assert yaml_value(option) is configutils.UNSET + + @pytest.mark.parametrize('method, value', [ + ('set_obj', {}), + ('set_str', '{}'), + ]) + def test_set_no_autoconfig_no_save(self, conf, qtbot, yaml_value, + method, value): + meth = getattr(conf, method) + option = 'bindings.default' + with qtbot.wait_signal(conf.changed): + meth(option, value) + + assert conf._values[option] + @pytest.mark.parametrize('method', ['set_obj', 'set_str']) def test_set_no_pattern(self, conf, method, qtbot): meth = getattr(conf, method) diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index c41e02b4c..c11850a15 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -48,6 +48,12 @@ def test_no_option_error_clash(): configexc.NoOptionError('opt', deleted=True, renamed='foo') +def test_no_autoconfig_error(): + e = configexc.NoAutoconfigError('opt') + expected = "The opt setting can only be set in config.py!" + assert str(e) == expected + + def test_backend_error(): e = configexc.BackendError('foo', usertypes.Backend.QtWebKit) expected = "The foo setting is not available with the QtWebKit backend!" diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index d3b3f5726..96f5d4976 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -223,6 +223,16 @@ class TestYaml: mode = 'persist' if persist else 'normal' assert data['tabs.mode_on_change']['global'] == mode + def test_bindings_default(self, yaml, autoconfig): + """Make sure bindings.default gets removed from autoconfig.yml.""" + autoconfig.write({'bindings.default': {'global': '{}'}}) + + yaml.load() + yaml._save() + + data = autoconfig.read() + assert 'bindings.default' not in data + def test_renamed_key_unknown_target(self, monkeypatch, yaml, autoconfig): """A key marked as renamed with invalid name should raise an error.""" -- cgit v1.2.3-54-g00ecf