diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 2f7fedb82..194ce919b 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -192,7 +192,8 @@ class Application(QApplication): # We didn't really initialize much so far, so we just quit hard. sys.exit(1) try: - self.keyconfig = keyconfparser.KeyConfigParser(confdir, 'keys') + self.keyconfig = keyconfparser.KeyConfigParser( + confdir, 'keys.conf') except keyconfparser.KeyConfigError as e: log.init.exception(e) errstr = "Error while reading key config:\n" @@ -730,7 +731,7 @@ class Application(QApplication): # event loop, so we can shut down immediately. self._shutdown(status) - def _shutdown(self, status): + def _shutdown(self, status): # noqa """Second stage of shutdown.""" log.destroy.debug("Stage 2 of shutting down...") # Remove eventfilter @@ -745,7 +746,10 @@ class Application(QApplication): if hasattr(self, 'config') and self.config is not None: to_save = [] if self.config.get('general', 'auto-save-config'): - to_save.append(("config", self.config.save)) + if hasattr(self, 'config'): + to_save.append(("config", self.config.save)) + if hasattr(self, 'keyconfig'): + to_save.append(("keyconfig", self.keyconfig.save)) to_save += [("window geometry", self._save_geometry), ("quickmarks", quickmarks.save)] if hasattr(self, 'cmd_history'): diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 3d0f6516e..267177617 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -26,7 +26,6 @@ we borrow some methods and classes from there where it makes sense. import os import os.path -import textwrap import functools import configparser import collections.abc @@ -34,7 +33,7 @@ import collections.abc from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication from qutebrowser.utils import log -from qutebrowser.config import configdata, iniparsers, configtypes +from qutebrowser.config import configdata, iniparsers, configtypes, textwrapper from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import message from qutebrowser.utils.usertypes import Completion @@ -89,7 +88,6 @@ class ConfigManager(QObject): sections: The configuration data as an OrderedDict. _fname: The filename to be opened. _configparser: A ReadConfigParser instance to load the config. - _wrapper_args: A dict with the default kwargs for the config wrappers. _configdir: The dictionary to read the config from and save it in. _configfile: The config file path. _interpolation: An configparser.Interpolation object @@ -113,12 +111,6 @@ class ConfigManager(QObject): self.sections = configdata.DATA self._configparser = iniparsers.ReadConfigParser(configdir, fname) self._configfile = os.path.join(configdir, fname) - self._wrapper_args = { - 'width': 72, - 'replace_whitespace': False, - 'break_long_words': False, - 'break_on_hyphens': False, - } self._configdir = configdir self._fname = fname self._interpolation = configparser.ExtendedInterpolation() @@ -146,9 +138,7 @@ class ConfigManager(QObject): def _str_section_desc(self, sectname): """Get the section description string for sectname.""" - wrapper = textwrap.TextWrapper(initial_indent='# ', - subsequent_indent='# ', - **self._wrapper_args) + wrapper = textwrapper.TextWrapper() lines = [] seclines = configdata.SECTION_DESC[sectname].splitlines() for secline in seclines: @@ -160,9 +150,8 @@ class ConfigManager(QObject): def _str_option_desc(self, sectname, sect): """Get the option description strings for sect/sectname.""" - wrapper = textwrap.TextWrapper(initial_indent='#' + ' ' * 5, - subsequent_indent='#' + ' ' * 5, - **self._wrapper_args) + wrapper = textwrapper.TextWrapper(initial_indent='#' + ' ' * 5, + subsequent_indent='#' + ' ' * 5) lines = [] if not getattr(sect, 'descriptions', None): return lines diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 5fca0394e..55e03da9f 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -780,7 +780,10 @@ DATA = collections.OrderedDict([ )), ]) -KEYBINDINGS = """ + +KEY_FIRST_COMMENT = """ +# vim: ft=conf +# # Bindings from a key(chain) to a command. # # For special keys (can't be part of a keychain), enclose them in `<`...`>`. @@ -795,412 +798,187 @@ KEYBINDINGS = """ # with Shift. For special keys (with `<>`-signs), you need to explicitely add # `Shift-` to match a key pressed with shift. You can bind multiple commands # by separating them with `;;`. - -[normal] - -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 - -open -t about:blank - ga - -tab-close - d - -tab-only - co - -tab-focus - T - -tab-move - gm - -tab-move - - gl - -tab-move + - gr - -tab-next - J - -tab-prev - K - -reload - r - -back - H - -forward - L - -hint - f - -hint all tab - F - -hint all tab-bg - ;b - -hint images - ;i - -hint images tab - ;I - -hint images tab-bg - .i - -hint links fill ":open {hint-url}" - ;o - -hint links fill ":open -t {hint-url}" - ;O - -hint links fill ":open -b {hint-url}" - .o - -hint links yank - ;y - -hint links yank-primary - ;Y - -hint links rapid - ;r - -hint links download - ;d - -scroll -50 0 - h - -scroll 0 50 - j - -scroll 0 -50 - k - -scroll 50 0 - l - -undo - u - -scroll-perc 0 - gg - -scroll-perc - G - -search-next - n - -search-prev - N - -enter-mode insert - i - -yank - yy - -yank -s - yY - -yank -t - yt - -yank -ts - yT - -paste - pp - -paste -s - pP - -paste -t - Pp - -paste -ts - PP - -quickmark-save - m - -set-cmd-text ":quickmark-load " - b - -set-cmd-text ":quickmark-load -t " - B - -save - sf - -set-cmd-text ":set " - ss - -set-cmd-text ":set -t " - sl - -set-cmd-text ":set keybind " - sk - -zoom-out - - - -zoom-in - + - -zoom - = - -prev-page - [[ - -next-page - ]] - -prev-page -t - {{ - -next-page -t - }} - -inspector - wi - -download-page - gd - -cancel-download - ad - -tab-focus last - - -enter-mode passthrough - - -quit - - -undo - - -tab-close - - -open -t about:blank - - -scroll-page 0 1 - - -scroll-page 0 -1 - - -scroll-page 0 0.5 - - -scroll-page 0 -0.5 - - -tab-focus 1 - - -tab-focus 2 - - -tab-focus 3 - - -tab-focus 4 - - -tab-focus 5 - - -tab-focus 6 - - -tab-focus 7 - - -tab-focus 8 - - -tab-focus 9 - - -back - - -home - - -stop - - -print - - -[insert,hint,passthrough,command,prompt] - -leave-mode - - - - -[passthrough] -# Keybindings for passthrough mode. -# -# Since normal keypresses are passed through, only special keys are supported -# in this section. - -[insert] - -# Since normal keypresses are passed through, only special keys are supported -# in this section. -# -# Useful hidden commands to map in this section: -# * `open-editor`: Open a texteditor with the focused field. - -open-editor - - -[hint] - -# Since normal keypresses are passed through, only special keys are supported -# in this section. -# -# Useful hidden commands to map in this section: -# -# * `follow-hint`: Follow the currently selected hint. - -follow-hint - - -[command,prompt] - -rl-backward-char - - -rl-forward-char - - -rl-backward-word - - -rl-forward-word - - -rl-beginning-of-line - - -rl-end-of-line - - -rl-unix-line-discard - - -rl-kill-line - - -rl-kill-word - - -rl-unix-word-rubout - - -rl-yank - - -rl-delete-char - - -rl-backward-delete-char - - - -[command] - -# Since normal keypresses are passed through, only special keys are -# supported in this mode. - -# Useful hidden commands to map in this section: -# -# * `command-history-prev`: Switch to previous command in history. -# * `command-history-next`: Switch to next command in history. -# * `completion-item-prev`: Select previous item in completion. -# * `completion-item-next`: Select next item in completion. -# * `command-accept`: Execute the command currently in the commandline. -# * `leave-mode`: Leave the command mode. - -command-history-prev - - -command-history-next - - -completion-item-prev - - -completion-item-prev - - -completion-item-next - - -completion-item-next - - -command-accept - - - - -[prompt] - -# You can bind normal keys in this mode, but they will be only active when a -# yes/no-prompt is asked. For other prompt modes, you can only bind special -# keys. - -# Useful hidden commands to map in this section: -# -# * `prompt-accept`: Confirm the entered value. -# * `prompt-yes`: Answer yes to a yes/no question. -# * `prompt-no`: Answer no to a yes/no question. -# * `leave-mode`: Leave the prompt mode. - -prompt-accept - - - - -prompt-yes - y - -prompt-no - n """ + +KEY_SECTION_DESC = { + 'all': "Keybindings active in all modes.", + 'normal': "Keybindings for normal mode.", + 'insert': ( + "Keybindings for insert mode.\n" + "Since normal keypresses are passed through, only special keys are " + "supported in this mode.\n" + "Useful hidden commands to map in this section:\n\n" + " * `open-editor`: Open a texteditor with the focused field."), + 'hint': ( + "Keybindings for hint mode.\n" + "Since normal keypresses are passed through, only special keys are " + "supported in this mode.\n" + "Useful hidden commands to map in this section:\n\n" + " * `follow-hint`: Follow the currently selected hint."), + 'passthrough': ( + "Keybindings for passthrough mode.\n" + "Since normal keypresses are passed through, only special keys are " + "supported in this mode."), + 'command': ( + "Keybindings for command mode.\n" + "Since normal keypresses are passed through, only special keys are " + "supported in this mode.\n" + "Useful hidden commands to map in this section:\n\n" + " * `command-history-prev`: Switch to previous command in history.\n" + " * `command-history-next`: Switch to next command in history.\n" + " * `completion-item-prev`: Select previous item in completion.\n" + " * `completion-item-next`: Select next item in completion.\n" + " * `command-accept`: Execute the command currently in the " + "commandline."), + 'prompt': ( + "Keybindings for prompts in the status line.\n" + "You can bind normal keys in this mode, but they will be only active " + "when a yes/no-prompt is asked. For other prompt modes, you can only " + "bind special keys.\n" + "Useful hidden commands to map in this section:\n\n" + " * `prompt-accept`: Confirm the entered value.\n" + " * `prompt-yes`: Answer yes to a yes/no question.\n" + " * `prompt-no`: Answer no to a yes/no question."), +} + + +KEY_DATA = collections.OrderedDict([ + ('all', collections.OrderedDict([ + ('leave-mode', ['', '']), + ])), + + ('normal', collections.OrderedDict([ + ('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']), + ('open -t about:blank', ['ga']), + ('tab-close', ['d']), + ('tab-only', ['co']), + ('tab-focus', ['T']), + ('tab-move', ['gm']), + ('tab-move -', ['gl']), + ('tab-move +', ['gr']), + ('tab-next', ['J']), + ('tab-prev', ['K']), + ('reload', ['r']), + ('back', ['H']), + ('forward', ['L']), + ('hint', ['f']), + ('hint all tab', ['F']), + ('hint all tab-bg', [';b']), + ('hint images', [';i']), + ('hint images tab', [';I']), + ('hint images tab-bg', ['.i']), + ('hint links fill ":open {hint-url}"', [';o']), + ('hint links fill ":open -t {hint-url}"', [';O']), + ('hint links fill ":open -b {hint-url}"', ['.o']), + ('hint links yank', [';y']), + ('hint links yank-primary', [';Y']), + ('hint links rapid', [';r']), + ('hint links download', [';d']), + ('scroll -50 0', ['h']), + ('scroll 0 50', ['j']), + ('scroll 0 -50', ['k']), + ('scroll 50 0', ['l']), + ('undo', ['u']), + ('scroll-perc 0', ['gg']), + ('scroll-perc', ['G']), + ('search-next', ['n']), + ('search-prev', ['N']), + ('enter-mode insert', ['i']), + ('yank', ['yy']), + ('yank -s', ['yY']), + ('yank -t', ['yt']), + ('yank -ts', ['yT']), + ('paste', ['pp']), + ('paste -s', ['pP']), + ('paste -t', ['Pp']), + ('paste -ts', ['PP']), + ('quickmark-save', ['m']), + ('set-cmd-text ":quickmark-load "', ['b']), + ('set-cmd-text ":quickmark-load -t "', ['B']), + ('save', ['sf']), + ('set-cmd-text ":set "', ['ss']), + ('set-cmd-text ":set -t "', ['sl']), + ('set-cmd-text ":set keybind "', ['sk']), + ('zoom-out', ['-']), + ('zoom-in', ['+']), + ('zoom', ['=']), + ('prev-page', ['[[']), + ('next-page', [']]']), + ('prev-page -t', ['{{']), + ('next-page -t', ['}}']), + ('inspector', ['wi']), + ('download-page', ['gd']), + ('cancel-download', ['ad']), + ('tab-focus last', ['']), + ('enter-mode passthrough', ['']), + ('quit', ['']), + ('undo', ['']), + ('tab-close', ['']), + ('open -t about:blank', ['']), + ('scroll-page 0 1', ['']), + ('scroll-page 0 -1', ['']), + ('scroll-page 0 0.5', ['']), + ('scroll-page 0 -0.5', ['']), + ('tab-focus 1', ['']), + ('tab-focus 2', ['']), + ('tab-focus 3', ['']), + ('tab-focus 4', ['']), + ('tab-focus 5', ['']), + ('tab-focus 6', ['']), + ('tab-focus 7', ['']), + ('tab-focus 8', ['']), + ('tab-focus 9', ['']), + ('back', ['']), + ('home', ['']), + ('stop', ['']), + ('print', ['']), + ])), + + ('insert', collections.OrderedDict([ + ('open-editor', ['']), + ])), + + ('hint', collections.OrderedDict([ + ('follow-hint', ['']), + ])), + + ('passthrough', {}), + + ('command', collections.OrderedDict([ + ('command-history-prev', ['']), + ('command-history-next', ['']), + ('completion-item-prev', ['']), + ('completion-item-prev', ['']), + ('completion-item-next', ['']), + ('completion-item-next', ['']), + ('command-accept', ['', '', '']), + ])), + + ('prompt', collections.OrderedDict([ + ('prompt-accept', ['', '']), + ('prompt-accept', ['']), + ('prompt-yes', ['y']), + ('prompt-no', ['n']), + ])), + + ('command,prompt', collections.OrderedDict([ + ('rl-backward-char', ['']), + ('rl-forward-char', ['']), + ('rl-backward-word', ['']), + ('rl-forward-word', ['']), + ('rl-beginning-of-line', ['']), + ('rl-end-of-line', ['']), + ('rl-unix-line-discard', ['']), + ('rl-kill-line', ['']), + ('rl-kill-word', ['']), + ('rl-unix-word-rubout', ['']), + ('rl-yank', ['']), + ('rl-delete-char', ['']), + ('rl-backward-delete-char', ['']), + ])), +]) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index fada5ae1b..15f6dd062 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1042,25 +1042,6 @@ class SearchEngineUrl(BaseType): url.errorString())) -class KeyBindingName(BaseType): - - """The name (keys) of a keybinding.""" - - def validate(self, value): - if not value: - if self.none_ok: - return - else: - raise ValidationError(value, "may not be empty!") - - -class KeyBinding(Command): - - """The command of a keybinding.""" - - pass - - class Encoding(BaseType): """Setting for a python encoding.""" diff --git a/qutebrowser/config/keyconfparser.py b/qutebrowser/config/keyconfparser.py index 9e8cd2dd9..6e7bd55d4 100644 --- a/qutebrowser/config/keyconfparser.py +++ b/qutebrowser/config/keyconfparser.py @@ -19,10 +19,10 @@ """Parser for the key configuration.""" - +import collections import os.path -from qutebrowser.config import configdata +from qutebrowser.config import configdata, textwrapper from qutebrowser.commands import cmdutils from qutebrowser.utils import log @@ -56,12 +56,67 @@ class KeyConfigParser: self._cur_section = None self._cur_command = None # Mapping of section name(s) to keybinding -> command dicts. - self.keybindings = {} + self.keybindings = collections.OrderedDict() if not os.path.exists(self._configfile): - log.init.debug("Creating initial keybinding config.") - with open(self._configfile, 'w', encoding='utf-8') as f: - f.write(configdata.KEYBINDINGS) - self._read() + self._load_default() + else: + self._read() + log.init.debug("Loaded bindings: {}".format(self.keybindings)) + + def __str__(self): + """Get the config as string.""" + lines = configdata.KEY_FIRST_COMMENT.strip('\n').splitlines() + lines.append('') + for sectname, sect in self.keybindings.items(): + lines.append('[{}]'.format(sectname)) + lines += self._str_section_desc(sectname) + lines.append('') + data = collections.OrderedDict() + for key, cmd in sect.items(): + if cmd in data: + data[cmd].append(key) + else: + data[cmd] = [key] + for cmd, keys in data.items(): + lines.append(cmd) + for k in keys: + lines.append(' ' * 4 + k) + lines.append('') + return '\n'.join(lines) + '\n' + + def _str_section_desc(self, sectname): + """Get the section description string for sectname.""" + wrapper = textwrapper.TextWrapper() + lines = [] + try: + seclines = configdata.KEY_SECTION_DESC[sectname].splitlines() + except KeyError: + return [] + else: + for secline in seclines: + if 'http://' in secline or 'https://' in secline: + lines.append('# ' + secline) + else: + lines += wrapper.wrap(secline) + return lines + + def save(self): + """Save the key config file.""" + log.destroy.debug("Saving key config to {}".format(self._configfile)) + with open(self._configfile, 'w', encoding='utf-8') as f: + f.write(str(self)) + + def _normalize_sectname(self, s): + """Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'.""" + return ','.join(sorted(s.split(','))) + + def _load_default(self): + """Load the built-in default keybindings.""" + for sectname, sect in configdata.KEY_DATA.items(): + sectname = self._normalize_sectname(sectname) + for command, keychains in sect.items(): + for e in keychains: + self._add_binding(sectname, e, command) def _read(self): """Read the config file from disk and parse it.""" @@ -72,7 +127,8 @@ class KeyConfigParser: if not line.strip() or line.startswith('#'): continue elif line.startswith('[') and line.endswith(']'): - self._cur_section = line[1:-1] + sectname = line[1:-1] + self._cur_section = self._normalize_sectname(sectname) elif line.startswith((' ', '\t')): line = line.strip() self._read_keybinding(line) @@ -101,10 +157,13 @@ class KeyConfigParser: "command!".format(line)) else: assert self._cur_section is not None - if self._cur_section not in self.keybindings: - self.keybindings[self._cur_section] = {} - section_bindings = self.keybindings[self._cur_section] - section_bindings[line] = self._cur_command + self._add_binding(self._cur_section, line, self._cur_command) + + def _add_binding(self, sectname, keychain, command): + """Add a new binding from keychain to command in section sectname.""" + if sectname not in self.keybindings: + self.keybindings[sectname] = collections.OrderedDict() + self.keybindings[sectname][keychain] = command def get_bindings_for(self, section): """Get a dict with all merged keybindings for a section.""" @@ -113,4 +172,8 @@ class KeyConfigParser: sects = [s.strip() for s in sectstring.split(',')] if any(s == section for s in sects): bindings.update(d) + try: + bindings.update(self.keybindings['all']) + except KeyError: + pass return bindings diff --git a/qutebrowser/config/textwrapper.py b/qutebrowser/config/textwrapper.py new file mode 100644 index 000000000..440c1a21a --- /dev/null +++ b/qutebrowser/config/textwrapper.py @@ -0,0 +1,39 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014 Florian Bruhin (The Compiler) +# +# 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 . + +"""Textwrapper used for config files.""" + +import textwrap + + +class TextWrapper(textwrap.TextWrapper): + + """Text wrapper customized to be used in configs.""" + + def __init__(self, *args, **kwargs): + kw = { + 'width': 72, + 'replace_whitespace': False, + 'break_long_words': False, + 'break_on_hyphens': False, + 'initial_indent': '# ', + 'subsequent_indent': '# ', + } + kw.update(kwargs) + super().__init__(*args, **kw) diff --git a/qutebrowser/test/config/test_configtypes.py b/qutebrowser/test/config/test_configtypes.py index aa17c9436..1c8aec76e 100644 --- a/qutebrowser/test/config/test_configtypes.py +++ b/qutebrowser/test/config/test_configtypes.py @@ -1733,32 +1733,6 @@ class SearchEngineUrlTests(unittest.TestCase): self.assertEqual(self.t.transform("foobar"), "foobar") -class KeyBindingNameTests(unittest.TestCase): - - """Test KeyBindingName.""" - - def setUp(self): - self.t = configtypes.KeyBindingName() - - def test_validate_empty(self): - """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configtypes.ValidationError): - self.t.validate('') - - def test_validate_empty_none_ok(self): - """Test validate with empty string and none_ok = True.""" - t = configtypes.KeyBindingName(none_ok=True) - t.validate('') - - def test_transform_empty(self): - """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) - - def test_transform(self): - """Test transform with a value.""" - self.assertEqual(self.t.transform("foobar"), "foobar") - - class WebSettingsFileTests(unittest.TestCase): """Test WebSettingsFile.""" diff --git a/qutebrowser/test/keyinput/test_basekeyparser.py b/qutebrowser/test/keyinput/test_basekeyparser.py index ebe806cc4..7f4c356c4 100644 --- a/qutebrowser/test/keyinput/test_basekeyparser.py +++ b/qutebrowser/test/keyinput/test_basekeyparser.py @@ -31,13 +31,15 @@ from qutebrowser.keyinput import basekeyparser from qutebrowser.test import stubs, helpers -CONFIG = {'test': {'': 'ctrla', - 'a': 'a', - 'ba': 'ba', - 'ax': 'ax', - 'ccc': 'ccc'}, - 'input': {'timeout': 100}, - 'test2': {'foo': 'bar', '': 'ctrlx'}} +CONFIG = {'input': {'timeout': 100}} + + +BINDINGS = {'test': {'': 'ctrla', + 'a': 'a', + 'ba': 'ba', + 'ax': 'ax', + 'ccc': 'ccc'}, + 'test2': {'foo': 'bar', '': 'ctrlx'}} def setUpModule(): @@ -51,6 +53,14 @@ def tearDownModule(): logging.disable(logging.NOTSET) +def _get_fake_application(): + """Construct a fake QApplication with a keyconfig.""" + app = stubs.FakeQApplication() + app.keyconfig = mock.Mock(spec=['get_bindings_for']) + app.keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s] + return app + + class SplitCountTests(unittest.TestCase): """Test the _split_count method. @@ -99,7 +109,7 @@ class ReadConfigTests(unittest.TestCase): """Test reading the config.""" def setUp(self): - basekeyparser.config = stubs.ConfigStub(CONFIG) + basekeyparser.QCoreApplication = _get_fake_application() basekeyparser.usertypes.Timer = mock.Mock() def test_read_config_invalid(self): @@ -136,7 +146,7 @@ class SpecialKeysTests(unittest.TestCase): autospec=True) patcher.start() self.addCleanup(patcher.stop) - basekeyparser.config = stubs.ConfigStub(CONFIG) + basekeyparser.QCoreApplication = _get_fake_application() self.kp = basekeyparser.BaseKeyParser() self.kp.execute = mock.Mock() self.kp.read_config('test') @@ -171,7 +181,7 @@ class KeyChainTests(unittest.TestCase): def setUp(self): """Set up mocks and read the test config.""" - basekeyparser.config = stubs.ConfigStub(CONFIG) + basekeyparser.QCoreApplication = _get_fake_application() self.timermock = mock.Mock() basekeyparser.usertypes.Timer = mock.Mock(return_value=self.timermock) self.kp = basekeyparser.BaseKeyParser(supports_chains=True, @@ -205,6 +215,7 @@ class KeyChainTests(unittest.TestCase): def test_ambigious_keychain(self): """Test ambigious keychain.""" + basekeyparser.config = stubs.ConfigStub(CONFIG) # We start with 'a' where the keychain gives us an ambigious result. # Then we check if the timer has been set up correctly self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) @@ -235,7 +246,7 @@ class CountTests(unittest.TestCase): """Test execute() with counts.""" def setUp(self): - basekeyparser.config = stubs.ConfigStub(CONFIG) + basekeyparser.QCoreApplication = _get_fake_application() basekeyparser.usertypes.Timer = mock.Mock() self.kp = basekeyparser.BaseKeyParser(supports_chains=True, supports_count=True) diff --git a/qutebrowser/test/stubs.py b/qutebrowser/test/stubs.py index a45cd4e12..df27030a8 100644 --- a/qutebrowser/test/stubs.py +++ b/qutebrowser/test/stubs.py @@ -110,8 +110,7 @@ class FakeQApplication: """Stub to insert as QApplication module.""" - def __init__(self, focus): - self.focusWidget = mock.Mock(return_value=focus) + def __init__(self): self.instance = mock.Mock(return_value=self) diff --git a/qutebrowser/test/utils/test_readline.py b/qutebrowser/test/utils/test_readline.py index adaa55c28..c45b29bb3 100644 --- a/qutebrowser/test/utils/test_readline.py +++ b/qutebrowser/test/utils/test_readline.py @@ -34,7 +34,8 @@ class NoneWidgetTests(unittest.TestCase): """Tests when the focused widget is None.""" def setUp(self): - readline.QApplication = stubs.FakeQApplication(None) + readline.QApplication = stubs.FakeQApplication() + readline.QApplication.focusWidget = mock.Mock(return_value=None) self.bridge = readline.ReadlineBridge() def test_none(self): @@ -52,7 +53,7 @@ class ReadlineBridgeTest(unittest.TestCase): def setUp(self): self.qle = mock.Mock() self.qle.__class__ = QLineEdit - readline.QApplication = stubs.FakeQApplication(self.qle) + readline.QApplication.focusWidget = mock.Mock(return_value=self.qle) self.bridge = readline.ReadlineBridge() def _set_selected_text(self, text):