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
|
AttributeError. This is by design. If you want to look something
|
||||||
up, set a default for it first."""
|
up, set a default for it first."""
|
||||||
|
|
||||||
# import standard python libs
|
|
||||||
import os, platform, sys, re
|
import os, platform, sys, re
|
||||||
|
|
||||||
# import unix-lib
|
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
# set this to true to enable debugging output
|
# set this to true to enable debugging output
|
||||||
|
# These should be moved somewhere better.
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
def dbg (log = ""):
|
def dbg (log = ""):
|
||||||
|
@ -49,12 +47,15 @@ def err (log = ""):
|
||||||
"""Print an error message"""
|
"""Print an error message"""
|
||||||
print >> sys.stderr, log
|
print >> sys.stderr, log
|
||||||
|
|
||||||
|
from configfile import ConfigFile, ConfigSyntaxError
|
||||||
|
|
||||||
class TerminatorConfig:
|
class TerminatorConfig:
|
||||||
"""This class is used as the base point of the config system"""
|
"""This class is used as the base point of the config system"""
|
||||||
callback = None
|
callback = None
|
||||||
sources = []
|
sources = []
|
||||||
|
|
||||||
def __init__ (self, sources):
|
def __init__ (self, sources):
|
||||||
|
self._keys = None
|
||||||
for source in sources:
|
for source in sources:
|
||||||
if isinstance(source, TerminatorConfValuestore):
|
if isinstance(source, TerminatorConfValuestore):
|
||||||
self.sources.append (source)
|
self.sources.append (source)
|
||||||
|
@ -63,6 +64,21 @@ class TerminatorConfig:
|
||||||
source = TerminatorConfValuestoreDefault ()
|
source = TerminatorConfValuestoreDefault ()
|
||||||
self.sources.append (source)
|
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):
|
def __getattr__ (self, keyname):
|
||||||
for source in self.sources:
|
for source in self.sources:
|
||||||
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
|
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
|
||||||
|
@ -133,6 +149,36 @@ class TerminatorConfValuestore:
|
||||||
'close_button_on_tab' : True,
|
'close_button_on_tab' : True,
|
||||||
'enable_real_transparency' : False,
|
'enable_real_transparency' : False,
|
||||||
'try_posix_regexp' : platform.system() != 'Linux',
|
'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):
|
def __getattr__ (self, keyname):
|
||||||
|
@ -150,11 +196,10 @@ class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
|
||||||
|
|
||||||
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
||||||
rcfilename = ""
|
rcfilename = ""
|
||||||
splitter = re.compile("\s*=\s*")
|
type = "RCFile"
|
||||||
#FIXME: use inotify to watch the rc, split __init__ into a parsing function
|
#FIXME: use inotify to watch the rc, split __init__ into a parsing function
|
||||||
# that can be re-used when rc changes.
|
# that can be re-used when rc changes.
|
||||||
def __init__ (self):
|
def __init__ (self):
|
||||||
self.type = "RCFile"
|
|
||||||
try:
|
try:
|
||||||
directory = os.environ['XDG_CONFIG_HOME']
|
directory = os.environ['XDG_CONFIG_HOME']
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
|
@ -163,25 +208,27 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
||||||
self.rcfilename = os.path.join(directory, "terminator/config")
|
self.rcfilename = os.path.join(directory, "terminator/config")
|
||||||
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
|
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
|
||||||
if os.path.exists (self.rcfilename):
|
if os.path.exists (self.rcfilename):
|
||||||
rcfile = open (self.rcfilename)
|
ini = ConfigFile(self.rcfilename)
|
||||||
rc = rcfile.readlines ()
|
|
||||||
rcfile.close ()
|
|
||||||
|
|
||||||
for item in rc:
|
|
||||||
try:
|
try:
|
||||||
item = item.strip ()
|
ini.parse()
|
||||||
if item and item[0] != '#':
|
except ConfigSyntaxError, e:
|
||||||
(key, value) = self.splitter.split (item)
|
print "There was an error parsing your configuration"
|
||||||
|
print str(e)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
for key in ini.settings:
|
||||||
|
try:
|
||||||
|
value = ini.settings[key]
|
||||||
# Check if this is actually a key we care about
|
# Check if this is actually a key we care about
|
||||||
if not self.defaults.has_key (key):
|
if not self.defaults.has_key (key):
|
||||||
|
# We should really mention this to the user
|
||||||
continue
|
continue
|
||||||
|
|
||||||
deftype = self.defaults[key].__class__.__name__
|
deftype = self.defaults[key].__class__.__name__
|
||||||
if deftype == 'bool':
|
if deftype == 'bool':
|
||||||
if value.lower () == 'true':
|
if value.lower () in ('true', 'yes', 'on'):
|
||||||
self.values[key] = True
|
self.values[key] = True
|
||||||
elif value.lower () == 'false':
|
elif value.lower () in ('false', 'no', 'off'):
|
||||||
self.values[key] = False
|
self.values[key] = False
|
||||||
else:
|
else:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
@ -192,6 +239,10 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
|
||||||
elif deftype == 'list':
|
elif deftype == 'list':
|
||||||
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
|
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
|
||||||
continue
|
continue
|
||||||
|
elif deftype == 'dict':
|
||||||
|
if type(value) != dict:
|
||||||
|
raise AttributeError
|
||||||
|
self.values[key] = value
|
||||||
else:
|
else:
|
||||||
self.values[key] = value
|
self.values[key] = value
|
||||||
|
|
||||||
|
|
|
@ -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 re, gtk
|
||||||
|
import terminatorlib.config
|
||||||
|
|
||||||
class KeymapError(Exception):
|
class KeymapError(Exception):
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
|
@ -9,38 +10,8 @@ class KeymapError(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
|
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
|
||||||
|
|
||||||
class TerminatorKeybindings:
|
|
||||||
Modifier = re.compile('<([^<]+)>')
|
Modifier = re.compile('<([^<]+)>')
|
||||||
keys = {
|
class TerminatorKeybindings:
|
||||||
'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 = {
|
modifiers = {
|
||||||
'ctrl': gtk.gdk.CONTROL_MASK,
|
'ctrl': gtk.gdk.CONTROL_MASK,
|
||||||
|
@ -51,6 +22,10 @@ class TerminatorKeybindings:
|
||||||
empty = {}
|
empty = {}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.configure({})
|
||||||
|
|
||||||
|
def configure(self, bindings):
|
||||||
|
self.keys = bindings
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
|
@ -74,14 +49,14 @@ class TerminatorKeybindings:
|
||||||
|
|
||||||
def _parsebinding(self, binding):
|
def _parsebinding(self, binding):
|
||||||
mask = 0
|
mask = 0
|
||||||
modifiers = re.findall(self.Modifier, binding)
|
modifiers = re.findall(Modifier, binding)
|
||||||
if modifiers:
|
if modifiers:
|
||||||
for modifier in modifiers:
|
for modifier in modifiers:
|
||||||
mask |= self._lookup_modifier(modifier)
|
mask |= self._lookup_modifier(modifier)
|
||||||
key = re.sub(self.Modifier, '', binding)
|
key = re.sub(Modifier, '', binding)
|
||||||
if key == '':
|
if key == '':
|
||||||
raise KeymapError('No key found')
|
raise KeymapError('No key found')
|
||||||
return (mask, re.sub(self.Modifier, '', binding))
|
return (mask, re.sub(Modifier, '', binding))
|
||||||
|
|
||||||
def _lookup_modifier(self, modifier):
|
def _lookup_modifier(self, modifier):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -118,9 +118,12 @@ class Terminator:
|
||||||
self.icon_theme = gtk.IconTheme ()
|
self.icon_theme = gtk.IconTheme ()
|
||||||
|
|
||||||
self.keybindings = TerminatorKeybindings()
|
self.keybindings = TerminatorKeybindings()
|
||||||
|
self.keybindings.configure(self.conf.keybindings)
|
||||||
if self.conf.f11_modifier:
|
if self.conf.f11_modifier:
|
||||||
self.keybindings.keys['full_screen'] = '<Ctrl><Shift>F11'
|
print "Warning: f11_modifier is no longer supported"
|
||||||
self.keybindings.reload()
|
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):
|
if self.conf.handle_size in xrange (0,6):
|
||||||
gtk.rc_parse_string("""
|
gtk.rc_parse_string("""
|
||||||
|
|
Loading…
Reference in New Issue