diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index d173eb1e0..98f2c67b3 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -34,6 +34,7 @@ from qutebrowser.keyinput import keyutils val = None instance = None key_instance = None +cache = None # Keeping track of all change filters to validate them later. change_filters = [] diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py new file mode 100644 index 000000000..dfead6664 --- /dev/null +++ b/qutebrowser/config/configcache.py @@ -0,0 +1,50 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Jay Kamat +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + + +"""Implementation of a basic config cache.""" + +from qutebrowser.config import config + + +class ConfigCache: + + """A 'high-performance' cache for the config system. + + Useful for areas which call out to the config system very frequently, DO + NOT modify the value returned, DO NOT require per-url settings, do not + change frequently, and do not require partially 'expanded' config paths. + + If any of these requirements are broken, you will get incorrect or slow + behavior. + """ + + def __init__(self) -> None: + self._cache = {} + config.instance.changed.connect(self._on_config_changed) + + def _on_config_changed(self, attr: str) -> None: + if attr in self._cache: + self._cache[attr] = config.instance.get(attr) + + def __getitem__(self, attr: str): + if attr not in self._cache: + assert not config.instance.get_opt(attr).supports_pattern + self._cache[attr] = config.instance.get(attr) + return self._cache[attr] diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 99a3ff91c..b8c50500f 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -28,6 +28,7 @@ from qutebrowser.config import (config, configdata, configfiles, configtypes, configexc, configcommands) from qutebrowser.utils import (objreg, usertypes, log, standarddir, message, qtutils) +from qutebrowser.config import configcache from qutebrowser.misc import msgbox, objects @@ -44,6 +45,7 @@ def early_init(args): config.instance = config.Config(yaml_config=yaml_config) config.val = config.ConfigContainer(config.instance) config.key_instance = config.KeyConfig(config.instance) + config.cache = configcache.ConfigCache() yaml_config.setParent(config.instance) for cf in config.change_filters: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 9c4473874..d6164fd49 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -719,9 +719,9 @@ class TabbedBrowser(QWidget): except TabDeletedError: # We can get signals for tabs we already deleted... return - start = config.val.colors.tabs.indicator.start - stop = config.val.colors.tabs.indicator.stop - system = config.val.colors.tabs.indicator.system + start = config.cache['colors.tabs.indicator.start'] + stop = config.cache['colors.tabs.indicator.stop'] + system = config.cache['colors.tabs.indicator.system'] color = utils.interpolate_color(start, stop, perc, system) self.widget.set_tab_indicator_color(idx, color) self.widget.update_tab_title(idx) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 02934a532..94e88ea71 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -139,9 +139,9 @@ class TabWidget(QTabWidget): """ tab = self.widget(idx) if tab.data.pinned: - fmt = config.val.tabs.title.format_pinned + fmt = config.cache['tabs.title.format_pinned'] else: - fmt = config.val.tabs.title.format + fmt = config.cache['tabs.title.format'] if (field is not None and (fmt is None or ('{' + field + '}') not in fmt)): @@ -604,7 +604,7 @@ class TabBar(QTabBar): minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: - confwidth = str(config.val.tabs.width) + confwidth = str(config.cache['tabs.width']) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) @@ -614,7 +614,7 @@ class TabBar(QTabBar): width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) else: - if config.val.tabs.pinned.shrink: + if config.cache['tabs.pinned.shrink']: pinned = self._tab_pinned(index) pinned_count, pinned_width = self._pinned_statistics() else: @@ -652,15 +652,15 @@ class TabBar(QTabBar): tab = QStyleOptionTab() self.initStyleOption(tab, idx) - # pylint: disable=bad-config-option - setting = config.val.colors.tabs - # pylint: enable=bad-config-option + setting = 'colors.tabs' if idx == selected: - setting = setting.selected - setting = setting.odd if (idx + 1) % 2 else setting.even + setting += '.selected' + setting += '.odd' if (idx + 1) % 2 else '.even' - tab.palette.setColor(QPalette.Window, setting.bg) - tab.palette.setColor(QPalette.WindowText, setting.fg) + tab.palette.setColor(QPalette.Window, + config.cache[setting + '.bg']) + tab.palette.setColor(QPalette.WindowText, + config.cache[setting + '.fg']) indicator_color = self.tab_indicator_color(idx) tab.palette.setColor(QPalette.Base, indicator_color) @@ -805,7 +805,7 @@ class TabBarStyle(QCommonStyle): elif element == QStyle.CE_TabBarTabLabel: if not opt.icon.isNull() and layouts.icon.isValid(): self._draw_icon(layouts, opt, p) - alignment = (config.val.tabs.title.alignment | + alignment = (config.cache['tabs.title.alignment'] | Qt.AlignVCenter | Qt.TextHideMnemonic) self._style.drawItemText(p, layouts.text, alignment, opt.palette, opt.state & QStyle.State_Enabled, @@ -878,8 +878,8 @@ class TabBarStyle(QCommonStyle): Return: A Layout object with two QRects. """ - padding = config.val.tabs.padding - indicator_padding = config.val.tabs.indicator.padding + padding = config.cache['tabs.padding'] + indicator_padding = config.cache['tabs.indicator.padding'] text_rect = QRect(opt.rect) if not text_rect.isValid(): @@ -890,7 +890,7 @@ class TabBarStyle(QCommonStyle): text_rect.adjust(padding.left, padding.top, -padding.right, -padding.bottom) - indicator_width = config.val.tabs.indicator.width + indicator_width = config.cache['tabs.indicator.width'] if indicator_width == 0: indicator_rect = QRect() else: @@ -933,9 +933,9 @@ class TabBarStyle(QCommonStyle): icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) - position = config.val.tabs.position + position = config.cache['tabs.position'] if (position in [QTabWidget.East, QTabWidget.West] and - config.val.tabs.favicons.show != 'never'): + config.cache['tabs.favicons.show'] != 'never'): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 32c5afc49..8d6f3ccae 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -147,6 +147,8 @@ PERFECT_FILES = [ 'config/configcommands.py'), ('tests/unit/config/test_configutils.py', 'config/configutils.py'), + ('tests/unit/config/test_configcache.py', + 'config/configcache.py'), ('tests/unit/utils/test_qtutils.py', 'utils/qtutils.py'), diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index ec562c3b4..5c16f894b 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -42,7 +42,7 @@ from PyQt5.QtNetwork import QNetworkCookieJar import helpers.stubs as stubsmod import helpers.utils from qutebrowser.config import (config, configdata, configtypes, configexc, - configfiles) + configfiles, configcache) from qutebrowser.utils import objreg, standarddir, utils, usertypes from qutebrowser.browser import greasemonkey from qutebrowser.browser.webkit import cookies @@ -253,6 +253,9 @@ def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub): container = config.ConfigContainer(conf) monkeypatch.setattr(config, 'val', container) + cache = configcache.ConfigCache() + monkeypatch.setattr(config, 'cache', cache) + try: configtypes.Font.monospace_fonts = container.fonts.monospace except configexc.NoOptionError: diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py new file mode 100644 index 000000000..54fd98d03 --- /dev/null +++ b/tests/unit/config/test_configcache.py @@ -0,0 +1,47 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Jay Kamat : +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Tests for qutebrowser.config.configcache.""" + +import pytest + +from qutebrowser.config import config + + +def test_configcache_except_pattern(config_stub): + with pytest.raises(AssertionError): + assert config.cache['content.javascript.enabled'] + + +def test_configcache_error_set(config_stub): + with pytest.raises(TypeError): + config.cache['content.javascript.enabled'] = True + + +def test_configcache_get(config_stub): + assert len(config.cache._cache) == 0 + assert not config.cache['auto_save.session'] + assert len(config.cache._cache) == 1 + assert not config.cache['auto_save.session'] + + +def test_configcache_get_after_set(config_stub): + assert not config.cache['auto_save.session'] + config_stub.val.auto_save.session = True + assert config.cache['auto_save.session']