Introduce a cut-down version of my .ini parser; this one doesn't do anything special with indents, and just builds a simple dict on parse. It does introduce the same error handling (other than that for indenting), so some previously working (but highly dubious) configs might break.
Hook up a [keybindings] section, to override the default ones now in config.py; teach TerminatorConfig to merge configured keybindings from available sources; gconf support shouldn't be far away.
This commit is contained in:
parent
0c49debaac
commit
c0ab0e3f34
|
@ -31,13 +31,11 @@ Trying to read a value that doesn't exist will raise an
|
|||
AttributeError. This is by design. If you want to look something
|
||||
up, set a default for it first."""
|
||||
|
||||
# import standard python libs
|
||||
import os, platform, sys, re
|
||||
|
||||
# import unix-lib
|
||||
import pwd
|
||||
|
||||
# set this to true to enable debugging output
|
||||
# These should be moved somewhere better.
|
||||
debug = False
|
||||
|
||||
def dbg (log = ""):
|
||||
|
@ -49,12 +47,15 @@ def err (log = ""):
|
|||
"""Print an error message"""
|
||||
print >> sys.stderr, log
|
||||
|
||||
from configfile import ConfigFile, ConfigSyntaxError
|
||||
|
||||
class TerminatorConfig:
|
||||
"""This class is used as the base point of the config system"""
|
||||
callback = None
|
||||
sources = []
|
||||
|
||||
def __init__ (self, sources):
|
||||
self._keys = None
|
||||
for source in sources:
|
||||
if isinstance(source, TerminatorConfValuestore):
|
||||
self.sources.append (source)
|
||||
|
@ -62,7 +63,22 @@ class TerminatorConfig:
|
|||
# We always add a default valuestore last so no valid config item ever goes unset
|
||||
source = TerminatorConfValuestoreDefault ()
|
||||
self.sources.append (source)
|
||||
|
||||
|
||||
def _merge_keybindings(self):
|
||||
if self._keys:
|
||||
return self._keys
|
||||
|
||||
self._keys = {}
|
||||
for source in reversed(self.sources):
|
||||
try:
|
||||
val = source.keybindings
|
||||
self._keys.update(val)
|
||||
except:
|
||||
pass
|
||||
return self._keys
|
||||
|
||||
keybindings = property(_merge_keybindings)
|
||||
|
||||
def __getattr__ (self, keyname):
|
||||
for source in self.sources:
|
||||
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
|
||||
|
@ -133,6 +149,36 @@ class TerminatorConfValuestore:
|
|||
'close_button_on_tab' : True,
|
||||
'enable_real_transparency' : False,
|
||||
'try_posix_regexp' : platform.system() != 'Linux',
|
||||
'keybindings' : {
|
||||
'zoom_in': '<Ctrl>plus',
|
||||
'zoom_out': '<Ctrl>minus',
|
||||
'zoom_normal': '<Ctrl>0',
|
||||
'new_root_tab': '<Ctrl><Shift><Alt>T',
|
||||
'new_tab': '<Ctrl><Shift>T',
|
||||
'go_next': '<Ctrl><Shift>N',
|
||||
'go_prev': '<Ctrl><Shift>P',
|
||||
'split_horiz': '<Ctrl><Shift>O',
|
||||
'split_vert': '<Ctrl><Shift>E',
|
||||
'close_term': '<Ctrl><Shift>W',
|
||||
'copy': '<Ctrl><Shift>C',
|
||||
'paste': '<Ctrl><Shift>V',
|
||||
'toggle_scrollbar': '<Ctrl><Shift>S',
|
||||
'search': '<Ctrl><Shift>F',
|
||||
'close_window': '<Ctrl><Shift>Q',
|
||||
'resize_up': '<Ctrl><Shift>Up',
|
||||
'resize_down': '<Ctrl><Shift>Down',
|
||||
'resize_left': '<Ctrl><Shift>Left',
|
||||
'resize_right': '<Ctrl><Shift>Right',
|
||||
'move_tab_right': '<Ctrl><Shift>Page_Down',
|
||||
'move_tab_left': '<Ctrl><Shift>Page_Up',
|
||||
'toggle_zoom': '<Ctrl><Shift>X',
|
||||
'scaled_zoom': '<Ctrl><Shift>Z',
|
||||
'next_tab': '<Ctrl>Page_Down',
|
||||
'prev_tab': '<Ctrl>Page_Up',
|
||||
'go_prev': '<Ctrl><Shift>Tab',
|
||||
'go_next': '<Ctrl>Tab',
|
||||
'full_screen': 'F11',
|
||||
}
|
||||
}
|
||||
|
||||
def __getattr__ (self, keyname):
|
||||
|
@ -150,11 +196,10 @@ class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
|
|||
|
||||
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
||||
rcfilename = ""
|
||||
splitter = re.compile("\s*=\s*")
|
||||
type = "RCFile"
|
||||
#FIXME: use inotify to watch the rc, split __init__ into a parsing function
|
||||
# that can be re-used when rc changes.
|
||||
def __init__ (self):
|
||||
self.type = "RCFile"
|
||||
try:
|
||||
directory = os.environ['XDG_CONFIG_HOME']
|
||||
except KeyError, e:
|
||||
|
@ -163,39 +208,45 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
|||
self.rcfilename = os.path.join(directory, "terminator/config")
|
||||
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
|
||||
if os.path.exists (self.rcfilename):
|
||||
rcfile = open (self.rcfilename)
|
||||
rc = rcfile.readlines ()
|
||||
rcfile.close ()
|
||||
ini = ConfigFile(self.rcfilename)
|
||||
try:
|
||||
ini.parse()
|
||||
except ConfigSyntaxError, e:
|
||||
print "There was an error parsing your configuration"
|
||||
print str(e)
|
||||
sys.exit()
|
||||
|
||||
for item in rc:
|
||||
for key in ini.settings:
|
||||
try:
|
||||
item = item.strip ()
|
||||
if item and item[0] != '#':
|
||||
(key, value) = self.splitter.split (item)
|
||||
value = ini.settings[key]
|
||||
# Check if this is actually a key we care about
|
||||
if not self.defaults.has_key (key):
|
||||
# We should really mention this to the user
|
||||
continue
|
||||
|
||||
# Check if this is actually a key we care about
|
||||
if not self.defaults.has_key (key):
|
||||
continue
|
||||
|
||||
deftype = self.defaults[key].__class__.__name__
|
||||
if deftype == 'bool':
|
||||
if value.lower () == 'true':
|
||||
self.values[key] = True
|
||||
elif value.lower () == 'false':
|
||||
self.values[key] = False
|
||||
else:
|
||||
raise AttributeError
|
||||
elif deftype == 'int':
|
||||
self.values[key] = int (value)
|
||||
elif deftype == 'float':
|
||||
self.values[key] = float (value)
|
||||
elif deftype == 'list':
|
||||
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
|
||||
continue
|
||||
deftype = self.defaults[key].__class__.__name__
|
||||
if deftype == 'bool':
|
||||
if value.lower () in ('true', 'yes', 'on'):
|
||||
self.values[key] = True
|
||||
elif value.lower () in ('false', 'no', 'off'):
|
||||
self.values[key] = False
|
||||
else:
|
||||
self.values[key] = value
|
||||
raise AttributeError
|
||||
elif deftype == 'int':
|
||||
self.values[key] = int (value)
|
||||
elif deftype == 'float':
|
||||
self.values[key] = float (value)
|
||||
elif deftype == 'list':
|
||||
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
|
||||
continue
|
||||
elif deftype == 'dict':
|
||||
if type(value) != dict:
|
||||
raise AttributeError
|
||||
self.values[key] = value
|
||||
else:
|
||||
self.values[key] = value
|
||||
|
||||
dbg (" VS_RCFile: Set value '%s' to '%s'"%(key, self.values[key]))
|
||||
dbg (" VS_RCFile: Set value '%s' to '%s'" % (key, self.values[key]))
|
||||
except Exception, e:
|
||||
dbg (" VS_RCFile: %s Exception handling: %s" % (type(e), item))
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/local/bin/python
|
||||
|
||||
import re
|
||||
from terminatorlib.config import dbg, debug
|
||||
|
||||
def group(*choices): return '(' + '|'.join(choices) + ')'
|
||||
def any(*choices): return group(*choices) + '*'
|
||||
def maybe(*choices): return group(*choices) + '?'
|
||||
|
||||
Newline = re.compile(r'[\r\n]+')
|
||||
Whitespace = r'[ \f\t]*'
|
||||
Comment = r'#[^\r\n]*'
|
||||
Ignore = re.compile(Whitespace + maybe(Comment) + maybe(r'[\r\n]+'))
|
||||
|
||||
WhitespaceRE = re.compile(Whitespace)
|
||||
CommentRE = re.compile(Comment)
|
||||
|
||||
QuotedStrings = {"'": re.compile(r"'([^'\r\n]*)'"), '"': re.compile(r'"([^"\r\n]*)"')}
|
||||
|
||||
Section = re.compile(r"\[([^\r\n\]]+)\][ \f\t]*")
|
||||
Setting = re.compile(r"(\w+)\s*=\s*")
|
||||
|
||||
Colorvalue = re.compile(r'(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})' + Whitespace + maybe(Comment))
|
||||
Barevalue = re.compile(r'([^\r\n#]+)' + Whitespace + maybe(Comment))
|
||||
|
||||
Tabsize = 8
|
||||
|
||||
class ConfigSyntaxError(Exception):
|
||||
def __init__(self, message, cf):
|
||||
self.message = message
|
||||
self.file = cf.filename
|
||||
self.lnum = cf._lnum
|
||||
self.pos = cf._pos
|
||||
self.line = cf._line
|
||||
|
||||
def __str__(self):
|
||||
return "File %s line %d:\n %s\n %s^\n%s" % (repr(self.file), self.lnum,
|
||||
self.line.rstrip(),
|
||||
' ' * self.pos, self.message)
|
||||
|
||||
class ConfigFile:
|
||||
def __init__(self, filename = None):
|
||||
self.filename = filename
|
||||
self.settings = {}
|
||||
|
||||
def _call_if_match(self, re, callable, group = 0):
|
||||
if self._pos == self._max:
|
||||
return False
|
||||
mo = re.match(self._line, self._pos)
|
||||
if mo:
|
||||
if callable:
|
||||
callable(mo.group(group))
|
||||
self._pos = mo.end()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _call_if_quoted_string(self, callable):
|
||||
if self._pos == self._max:
|
||||
return False
|
||||
chr = self._line[self._pos]
|
||||
if chr in '"\'':
|
||||
string = ''
|
||||
while True:
|
||||
mo = QuotedStrings[chr].match(self._line, self._pos)
|
||||
if mo is None:
|
||||
raise ConfigSyntaxError("Unterminated quoted string", self)
|
||||
self._pos = mo.end()
|
||||
if self._line[self._pos - 2] == '\\':
|
||||
string += mo.group(1)[0:-1] + chr
|
||||
self._pos -= 1
|
||||
else:
|
||||
string += mo.group(1)
|
||||
break
|
||||
callable(string)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def parse(self):
|
||||
file = open(self.filename)
|
||||
rc = file.readlines()
|
||||
file.close()
|
||||
|
||||
self._indents = [0]
|
||||
self._pos = 0
|
||||
self._max = 0
|
||||
self._lnum = 0
|
||||
self._line = ''
|
||||
|
||||
self._currsection = None
|
||||
self._cursetting = None
|
||||
|
||||
for self._line in rc:
|
||||
self._lnum += 1
|
||||
self._pos = 0
|
||||
self._max = len(self._line)
|
||||
dbg("Line %d: %s" % (self._lnum, repr(self._line)))
|
||||
|
||||
self._call_if_match(WhitespaceRE, None)
|
||||
|
||||
# [Section]
|
||||
self._call_if_match(Section, self._section, 1)
|
||||
# setting =
|
||||
if self._call_if_match(Setting, self._setting, 1):
|
||||
# "quoted value"
|
||||
if not self._call_if_quoted_string(self._value):
|
||||
# #000000 # colour that would otherwise be a comment
|
||||
if not self._call_if_match(Colorvalue, self._value, 1):
|
||||
# bare value
|
||||
if not self._call_if_match(Barevalue, self._value, 1):
|
||||
raise ConfigSyntaxError("Setting without a value", self)
|
||||
|
||||
self._call_if_match(Ignore, None)
|
||||
|
||||
if self._line[self._pos:] != '':
|
||||
raise ConfigSyntaxError("Unexpected token", self)
|
||||
|
||||
def _section(self, section):
|
||||
dbg("Section %s" % repr(section))
|
||||
self._currsection = section
|
||||
|
||||
def _setting(self, setting):
|
||||
dbg("Setting %s" % repr(setting))
|
||||
self._currsetting = setting
|
||||
|
||||
def _value(self, value):
|
||||
dbg("Value %s" % repr(value))
|
||||
if self._currsection is not None:
|
||||
self.settings.setdefault(self._currsection, {})[self._currsetting] = value
|
||||
else:
|
||||
self.settings[self._currsetting] = value
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
import re, gtk
|
||||
import terminatorlib.config
|
||||
|
||||
class KeymapError(Exception):
|
||||
def __init__(self, value):
|
||||
|
@ -9,38 +10,8 @@ class KeymapError(Exception):
|
|||
def __str__(self):
|
||||
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
|
||||
|
||||
Modifier = re.compile('<([^<]+)>')
|
||||
class TerminatorKeybindings:
|
||||
Modifier = re.compile('<([^<]+)>')
|
||||
keys = {
|
||||
'zoom_in': '<Ctrl>plus',
|
||||
'zoom_out': '<Ctrl>minus',
|
||||
'zoom_normal': '<Ctrl>0',
|
||||
'new_root_tab': '<Ctrl><Shift><Alt>T',
|
||||
'new_tab': '<Ctrl><Shift>T',
|
||||
'go_next': '<Ctrl><Shift>N',
|
||||
'go_prev': '<Ctrl><Shift>P',
|
||||
'split_horiz': '<Ctrl><Shift>O',
|
||||
'split_vert': '<Ctrl><Shift>E',
|
||||
'close_term': '<Ctrl><Shift>W',
|
||||
'copy': '<Ctrl><Shift>C',
|
||||
'paste': '<Ctrl><Shift>V',
|
||||
'toggle_scrollbar': '<Ctrl><Shift>S',
|
||||
'search': '<Ctrl><Shift>F',
|
||||
'close_window': '<Ctrl><Shift>Q',
|
||||
'resize_up': '<Ctrl><Shift>Up',
|
||||
'resize_down': '<Ctrl><Shift>Down',
|
||||
'resize_left': '<Ctrl><Shift>Left',
|
||||
'resize_right': '<Ctrl><Shift>Right',
|
||||
'move_tab_right': '<Ctrl><Shift>Page_Down',
|
||||
'move_tab_left': '<Ctrl><Shift>Page_Up',
|
||||
'toggle_zoom': '<Ctrl><Shift>X',
|
||||
'scaled_zoom': '<Ctrl><Shift>Z',
|
||||
'next_tab': '<Ctrl>Page_Down',
|
||||
'prev_tab': '<Ctrl>Page_Up',
|
||||
'go_prev': '<Ctrl><Shift>Tab',
|
||||
'go_next': '<Ctrl>Tab',
|
||||
'full_screen': 'F11',
|
||||
}
|
||||
|
||||
modifiers = {
|
||||
'ctrl': gtk.gdk.CONTROL_MASK,
|
||||
|
@ -51,6 +22,10 @@ class TerminatorKeybindings:
|
|||
empty = {}
|
||||
|
||||
def __init__(self):
|
||||
self.configure({})
|
||||
|
||||
def configure(self, bindings):
|
||||
self.keys = bindings
|
||||
self.reload()
|
||||
|
||||
def reload(self):
|
||||
|
@ -74,14 +49,14 @@ class TerminatorKeybindings:
|
|||
|
||||
def _parsebinding(self, binding):
|
||||
mask = 0
|
||||
modifiers = re.findall(self.Modifier, binding)
|
||||
modifiers = re.findall(Modifier, binding)
|
||||
if modifiers:
|
||||
for modifier in modifiers:
|
||||
mask |= self._lookup_modifier(modifier)
|
||||
key = re.sub(self.Modifier, '', binding)
|
||||
key = re.sub(Modifier, '', binding)
|
||||
if key == '':
|
||||
raise KeymapError('No key found')
|
||||
return (mask, re.sub(self.Modifier, '', binding))
|
||||
return (mask, re.sub(Modifier, '', binding))
|
||||
|
||||
def _lookup_modifier(self, modifier):
|
||||
try:
|
||||
|
|
|
@ -118,9 +118,12 @@ class Terminator:
|
|||
self.icon_theme = gtk.IconTheme ()
|
||||
|
||||
self.keybindings = TerminatorKeybindings()
|
||||
self.keybindings.configure(self.conf.keybindings)
|
||||
if self.conf.f11_modifier:
|
||||
self.keybindings.keys['full_screen'] = '<Ctrl><Shift>F11'
|
||||
self.keybindings.reload()
|
||||
print "Warning: f11_modifier is no longer supported"
|
||||
print "Add the following to the end of your terminator config:"
|
||||
print "[keybindings]"
|
||||
print "full_screen = <Ctrl><Shift>F11"
|
||||
|
||||
if self.conf.handle_size in xrange (0,6):
|
||||
gtk.rc_parse_string("""
|
||||
|
|
Loading…
Reference in New Issue