Full read-write support for key config.
This commit is contained in:
parent
414ab88a0e
commit
e3d16f3bbe
@ -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'):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
<Ctrl-Tab>
|
||||
|
||||
enter-mode passthrough
|
||||
<Ctrl-V>
|
||||
|
||||
quit
|
||||
<Ctrl-Q>
|
||||
|
||||
undo
|
||||
<Ctrl-Shift-T>
|
||||
|
||||
tab-close
|
||||
<Ctrl-W>
|
||||
|
||||
open -t about:blank
|
||||
<Ctrl-T>
|
||||
|
||||
scroll-page 0 1
|
||||
<Ctrl-F>
|
||||
|
||||
scroll-page 0 -1
|
||||
<Ctrl-B>
|
||||
|
||||
scroll-page 0 0.5
|
||||
<Ctrl-D>
|
||||
|
||||
scroll-page 0 -0.5
|
||||
<Ctrl-U>
|
||||
|
||||
tab-focus 1
|
||||
<Alt-1>
|
||||
|
||||
tab-focus 2
|
||||
<Alt-2>
|
||||
|
||||
tab-focus 3
|
||||
<Alt-3>
|
||||
|
||||
tab-focus 4
|
||||
<Alt-4>
|
||||
|
||||
tab-focus 5
|
||||
<Alt-5>
|
||||
|
||||
tab-focus 6
|
||||
<Alt-6>
|
||||
|
||||
tab-focus 7
|
||||
<Alt-7>
|
||||
|
||||
tab-focus 8
|
||||
<Alt-8>
|
||||
|
||||
tab-focus 9
|
||||
<Alt-9>
|
||||
|
||||
back
|
||||
<Backspace>
|
||||
|
||||
home
|
||||
<Ctrl-h>
|
||||
|
||||
stop
|
||||
<Ctrl-s>
|
||||
|
||||
print
|
||||
<Ctrl-Alt-p>
|
||||
|
||||
[insert,hint,passthrough,command,prompt]
|
||||
|
||||
leave-mode
|
||||
<Escape>
|
||||
<Ctrl-N>
|
||||
<Ctrl-[>
|
||||
|
||||
[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
|
||||
<Ctrl-E>
|
||||
|
||||
[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
|
||||
<Return>
|
||||
|
||||
[command,prompt]
|
||||
|
||||
rl-backward-char
|
||||
<Ctrl-B>
|
||||
|
||||
rl-forward-char
|
||||
<Ctrl-F>
|
||||
|
||||
rl-backward-word
|
||||
<Alt-B>
|
||||
|
||||
rl-forward-word
|
||||
<Alt-F>
|
||||
|
||||
rl-beginning-of-line
|
||||
<Ctrl-A>
|
||||
|
||||
rl-end-of-line
|
||||
<Ctrl-E>
|
||||
|
||||
rl-unix-line-discard
|
||||
<Ctrl-U>
|
||||
|
||||
rl-kill-line
|
||||
<Ctrl-K>
|
||||
|
||||
rl-kill-word
|
||||
<Alt-D>
|
||||
|
||||
rl-unix-word-rubout
|
||||
<Ctrl-W>
|
||||
|
||||
rl-yank
|
||||
<Ctrl-Y>
|
||||
|
||||
rl-delete-char
|
||||
<Ctrl-?>
|
||||
|
||||
rl-backward-delete-char
|
||||
<Ctrl-H>
|
||||
|
||||
|
||||
[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
|
||||
<Ctrl-P>
|
||||
|
||||
command-history-next
|
||||
<Ctrl-N>
|
||||
|
||||
completion-item-prev
|
||||
<Shift-Tab>
|
||||
|
||||
completion-item-prev
|
||||
<Up>
|
||||
|
||||
completion-item-next
|
||||
<Tab>
|
||||
|
||||
completion-item-next
|
||||
<Down>
|
||||
|
||||
command-accept
|
||||
<Return>
|
||||
<Ctrl-J>
|
||||
<Shift-Return>
|
||||
|
||||
[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
|
||||
<Return>
|
||||
<Shift-Return>
|
||||
<Ctrl-J>
|
||||
|
||||
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', ['<Escape>', '<Ctrl-[>']),
|
||||
])),
|
||||
|
||||
('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', ['<Ctrl-Tab>']),
|
||||
('enter-mode passthrough', ['<Ctrl-V>']),
|
||||
('quit', ['<Ctrl-Q>']),
|
||||
('undo', ['<Ctrl-Shift-T>']),
|
||||
('tab-close', ['<Ctrl-W>']),
|
||||
('open -t about:blank', ['<Ctrl-T>']),
|
||||
('scroll-page 0 1', ['<Ctrl-F>']),
|
||||
('scroll-page 0 -1', ['<Ctrl-B>']),
|
||||
('scroll-page 0 0.5', ['<Ctrl-D>']),
|
||||
('scroll-page 0 -0.5', ['<Ctrl-U>']),
|
||||
('tab-focus 1', ['<Alt-1>']),
|
||||
('tab-focus 2', ['<Alt-2>']),
|
||||
('tab-focus 3', ['<Alt-3>']),
|
||||
('tab-focus 4', ['<Alt-4>']),
|
||||
('tab-focus 5', ['<Alt-5>']),
|
||||
('tab-focus 6', ['<Alt-6>']),
|
||||
('tab-focus 7', ['<Alt-7>']),
|
||||
('tab-focus 8', ['<Alt-8>']),
|
||||
('tab-focus 9', ['<Alt-9>']),
|
||||
('back', ['<Backspace>']),
|
||||
('home', ['<Ctrl-h>']),
|
||||
('stop', ['<Ctrl-s>']),
|
||||
('print', ['<Ctrl-Alt-p>']),
|
||||
])),
|
||||
|
||||
('insert', collections.OrderedDict([
|
||||
('open-editor', ['<Ctrl-E>']),
|
||||
])),
|
||||
|
||||
('hint', collections.OrderedDict([
|
||||
('follow-hint', ['<Return>']),
|
||||
])),
|
||||
|
||||
('passthrough', {}),
|
||||
|
||||
('command', collections.OrderedDict([
|
||||
('command-history-prev', ['<Ctrl-P>']),
|
||||
('command-history-next', ['<Ctrl-N>']),
|
||||
('completion-item-prev', ['<Shift-Tab>']),
|
||||
('completion-item-prev', ['<Up>']),
|
||||
('completion-item-next', ['<Tab>']),
|
||||
('completion-item-next', ['<Down>']),
|
||||
('command-accept', ['<Return>', '<Ctrl-J>', '<Shift-Return>']),
|
||||
])),
|
||||
|
||||
('prompt', collections.OrderedDict([
|
||||
('prompt-accept', ['<Return>', '<Ctrl-J>']),
|
||||
('prompt-accept', ['<Shift-Return>']),
|
||||
('prompt-yes', ['y']),
|
||||
('prompt-no', ['n']),
|
||||
])),
|
||||
|
||||
('command,prompt', collections.OrderedDict([
|
||||
('rl-backward-char', ['<Ctrl-B>']),
|
||||
('rl-forward-char', ['<Ctrl-F>']),
|
||||
('rl-backward-word', ['<Alt-B>']),
|
||||
('rl-forward-word', ['<Alt-F>']),
|
||||
('rl-beginning-of-line', ['<Ctrl-A>']),
|
||||
('rl-end-of-line', ['<Ctrl-E>']),
|
||||
('rl-unix-line-discard', ['<Ctrl-U>']),
|
||||
('rl-kill-line', ['<Ctrl-K>']),
|
||||
('rl-kill-word', ['<Alt-D>']),
|
||||
('rl-unix-word-rubout', ['<Ctrl-W>']),
|
||||
('rl-yank', ['<Ctrl-Y>']),
|
||||
('rl-delete-char', ['<Ctrl-?>']),
|
||||
('rl-backward-delete-char', ['<Ctrl-H>']),
|
||||
])),
|
||||
])
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
39
qutebrowser/config/textwrapper.py
Normal file
39
qutebrowser/config/textwrapper.py
Normal file
@ -0,0 +1,39 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""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)
|
@ -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."""
|
||||
|
@ -31,13 +31,15 @@ from qutebrowser.keyinput import basekeyparser
|
||||
from qutebrowser.test import stubs, helpers
|
||||
|
||||
|
||||
CONFIG = {'test': {'<Ctrl-a>': 'ctrla',
|
||||
'a': 'a',
|
||||
'ba': 'ba',
|
||||
'ax': 'ax',
|
||||
'ccc': 'ccc'},
|
||||
'input': {'timeout': 100},
|
||||
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
|
||||
CONFIG = {'input': {'timeout': 100}}
|
||||
|
||||
|
||||
BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
|
||||
'a': 'a',
|
||||
'ba': 'ba',
|
||||
'ax': 'ax',
|
||||
'ccc': 'ccc'},
|
||||
'test2': {'foo': 'bar', '<Ctrl+X>': '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)
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user