Allow binding keys.

This commit is contained in:
Florian Bruhin 2014-09-09 23:05:28 +02:00
parent 277dab4069
commit 4fde56a942
3 changed files with 57 additions and 9 deletions

View File

@ -415,9 +415,10 @@ class Application(QApplication):
# config # config
self.config.style_changed.connect(style.get_stylesheet.cache_clear) self.config.style_changed.connect(style.get_stylesheet.cache_clear)
for obj in (tabs, completion, self.mainwindow, self.cmd_history, for obj in (tabs, completion, self.mainwindow, self.cmd_history,
websettings, kp[utypes.KeyMode.normal], self.modeman, websettings, self.modeman, status, status.txt):
status, status.txt):
self.config.changed.connect(obj.on_config_changed) self.config.changed.connect(obj.on_config_changed)
for obj in kp.values():
self.keyconfig.changed.connect(obj.on_keyconfig_changed)
# statusbar # statusbar
# FIXME some of these probably only should be triggered on mainframe # FIXME some of these probably only should be triggered on mainframe

View File

@ -22,8 +22,10 @@
import collections import collections
import os.path import os.path
from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.config import configdata, textwrapper from qutebrowser.config import configdata, textwrapper
from qutebrowser.commands import cmdutils from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import log from qutebrowser.utils import log
@ -40,17 +42,28 @@ class KeyConfigError(Exception):
self.lineno = None self.lineno = None
class KeyConfigParser: class KeyConfigParser(QObject):
"""Parser for the keybind config.""" """Parser for the keybind config.
def __init__(self, configdir, fname): Attributes:
FIXME
Signals:
changed: Emitted when the config has changed.
arg: Name of the mode which was changed.
"""
changed = pyqtSignal(str)
def __init__(self, configdir, fname, parent=None):
"""Constructor. """Constructor.
Args: Args:
configdir: The directory to save the configs in. configdir: The directory to save the configs in.
fname: The filename of the config. fname: The filename of the config.
""" """
super().__init__(parent)
self._configdir = configdir self._configdir = configdir
self._configfile = os.path.join(self._configdir, fname) self._configfile = os.path.join(self._configdir, fname)
self._cur_section = None self._cur_section = None
@ -106,6 +119,35 @@ class KeyConfigParser:
with open(self._configfile, 'w', encoding='utf-8') as f: with open(self._configfile, 'w', encoding='utf-8') as f:
f.write(str(self)) f.write(str(self))
@cmdutils.register(instance='keyconfig')
def bind(self, key, command, mode=None):
"""Bind a key to a command.
//
FIXME: We should use the KeyMode enum here, and some argparser type for
a comma-separated list of enums.
Args:
key: The keychain or special key (inside <...>) to bind.
command: The command to execute.
mode: A comma-separated list of modes to bind the key in
(default: normal mode).
"""
if mode is None:
mode = 'normal'
for m in mode.split(','):
if m not in configdata.KEY_DATA:
raise cmdexc.CommandError("Invalid mode {}!".format(m))
if command.split(maxsplit=1)[0] not in cmdutils.cmd_dict:
raise cmdexc.CommandError("Invalid command {}!".format(command))
try:
self._add_binding(mode, key, command)
except KeyConfigError as e:
raise cmdexc.CommandError(e)
for m in mode.split(','):
self.changed.emit(m)
def _normalize_sectname(self, s): def _normalize_sectname(self, s):
"""Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'.""" """Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'."""
return ','.join(sorted(s.split(','))) return ','.join(sorted(s.split(',')))
@ -120,6 +162,7 @@ class KeyConfigParser:
for command, keychains in sect.items(): for command, keychains in sect.items():
for e in keychains: for e in keychains:
self._add_binding(sectname, e, command) self._add_binding(sectname, e, command)
self.changed.emit(sectname)
def _read(self): def _read(self):
"""Read the config file from disk and parse it.""" """Read the config file from disk and parse it."""
@ -141,6 +184,8 @@ class KeyConfigParser:
except KeyConfigError as e: except KeyConfigError as e:
e.lineno = i e.lineno = i
raise raise
for sectname in self.keybindings:
self.changed.emit(sectname)
def _read_command(self, line): def _read_command(self, line):
"""Read a command from a line.""" """Read a command from a line."""
@ -164,6 +209,8 @@ class KeyConfigParser:
def _add_binding(self, sectname, keychain, command): def _add_binding(self, sectname, keychain, command):
"""Add a new binding from keychain to command in section sectname.""" """Add a new binding from keychain to command in section sectname."""
log.keyboard.debug("Adding binding {} -> {} in mode {}.".format(
keychain, command, sectname))
if sectname not in self.keybindings: if sectname not in self.keybindings:
self.keybindings[sectname] = collections.OrderedDict() self.keybindings[sectname] = collections.OrderedDict()
if keychain in self.get_bindings_for(sectname): if keychain in self.get_bindings_for(sectname):

View File

@ -345,11 +345,11 @@ class BaseKeyParser(QObject):
""" """
raise NotImplementedError raise NotImplementedError
@pyqtSlot(str, str) @pyqtSlot(str)
def on_config_changed(self, section, _option): def on_keyconfig_changed(self, mode):
"""Re-read the config if a keybinding was changed.""" """Re-read the config if a keybinding was changed."""
if self._modename is None: if self._modename is None:
raise AttributeError("on_config_changed called but no section " raise AttributeError("on_config_changed called but no section "
"defined!") "defined!")
if section == self._modename: if mode == self._modename:
self.read_config() self.read_config()