Make bindings.default only settable in autoconfig.yml

Fixes #3131
This commit is contained in:
Florian Bruhin 2018-03-07 18:30:44 +01:00
parent 8a60855b88
commit 34815f5cf8
14 changed files with 85 additions and 8 deletions

View File

@ -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

View File

@ -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: <<types,Dict>>
Default:

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -2177,6 +2177,7 @@ bindings.key_mappings:
`bindings.commands`), the mapping is ignored.
bindings.default:
no_autoconfig: true
default:
normal:
<Escape>: clear-keychain ;; search ;; fullscreen --leave

View File

@ -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."""

View File

@ -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):

View File

@ -33,7 +33,7 @@ input { width: 98%; }
<th>Setting</th>
<th>Value</th>
</tr>
{% for option in configdata.DATA.values() %}
{% for option in configdata.DATA.values() if not option.no_autoconfig %}
<tr>
<!-- FIXME: convert to string properly -->
<td class="setting">{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }})

View File

@ -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(',', '&#44;')
f.write('Type: <<types,{typ}>>\n'.format(typ=typ))

View File

@ -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": {"<Ctrl+q>": "quit", "ZQ": "quit", '
'"I": "invalid", "d": "scroll down"}}')),
('bindings.default', 'Default keybindings',
'{"normal": {"<Ctrl+q>": "quit", "d": "tab-close"}}'),
('content.javascript.enabled', 'Enable/Disable JavaScript',
'true'),
]

View File

@ -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)

View File

@ -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!"

View File

@ -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."""