blob: ccb7f8aeb938ea31669d4faea25f5bccef04d43b [file] [log] [blame]
Steven M. Gava2a63a072001-10-26 06:50:54 +00001"""
Steven M. Gavaad4f5322002-01-03 12:05:17 +00002Provides access to stored idle configuration information.
Steven M. Gavaad4f5322002-01-03 12:05:17 +00003"""
Steven M. Gavac5976402002-01-04 03:06:08 +00004# Throughout this module there is an emphasis on returning useable defaults
5# when a problem occurs in returning a requested configuration value back to
6# idle. This is to allow idle to continue to function in spite of errors in
7# the retrieval of config information. When a default is returned instead of
8# a requested config value, a message is printed to stderr to aid in
9# configuration problem notification and resolution.
10
Steven M. Gavac11ccf32001-09-24 09:43:17 +000011import os
12import sys
13from ConfigParser import ConfigParser, NoOptionError, NoSectionError
14
15class IdleConfParser(ConfigParser):
16 """
17 A ConfigParser specialised for idle configuration file handling
18 """
19 def __init__(self, cfgFile, cfgDefaults=None):
20 """
21 cfgFile - string, fully specified configuration file name
22 """
23 self.file=cfgFile
24 ConfigParser.__init__(self,defaults=cfgDefaults)
25
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000026 def Get(self, section, option, type=None, default=None):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000027 """
28 Get an option value for given section/option or return default.
29 If type is specified, return as type.
30 """
Steven M. Gava41a85322001-10-29 08:05:34 +000031 if type=='bool':
32 getVal=self.getboolean
33 elif type=='int':
34 getVal=self.getint
35 else:
36 getVal=self.get
Steven M. Gavac11ccf32001-09-24 09:43:17 +000037 if self.has_option(section,option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000038 #return getVal(section, option, raw, vars, default)
Steven M. Gava429a86a2001-10-23 10:42:12 +000039 return getVal(section, option)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000040 else:
41 return default
Steven M. Gavac11ccf32001-09-24 09:43:17 +000042
Steven M. Gavac11ccf32001-09-24 09:43:17 +000043 def GetOptionList(self,section):
44 """
45 Get an option list for given section
46 """
47 if self.has_section:
48 return self.options(section)
49 else: #return a default value
50 return []
51
Steven M. Gavac11ccf32001-09-24 09:43:17 +000052 def Load(self):
53 """
54 Load the configuration file from disk
55 """
56 self.read(self.file)
57
58class IdleUserConfParser(IdleConfParser):
59 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000060 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000061 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000062
63 def AddSection(self,section):
64 """
65 if section doesn't exist, add it
66 """
67 if not self.has_section(section):
68 self.add_section(section)
69
70 def RemoveEmptySections(self):
71 """
72 remove any sections that have no options
73 """
74 for section in self.sections():
75 if not self.GetOptionList(section):
76 self.remove_section(section)
77
78 def IsEmpty(self):
79 """
80 Remove empty sections and then return 1 if parser has no sections
81 left, else return 0.
82 """
83 self.RemoveEmptySections()
84 if self.sections():
85 return 0
86 else:
87 return 1
88
89 def RemoveOption(self,section,option):
90 """
91 If section/option exists, remove it.
92 Returns 1 if option was removed, 0 otherwise.
93 """
94 if self.has_section(section):
95 return self.remove_option(section,option)
96
97 def SetOption(self,section,option,value):
98 """
99 Sets option to value, adding section if required.
100 Returns 1 if option was added or changed, otherwise 0.
101 """
102 if self.has_option(section,option):
103 if self.get(section,option)==value:
104 return 0
105 else:
106 self.set(section,option,value)
107 return 1
108 else:
109 if not self.has_section(section):
110 self.add_section(section)
111 self.set(section,option,value)
112 return 1
113
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000114 def Save(self):
115 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000116 If config isn't empty, write file to disk. If config is empty,
117 remove the file from disk if it exists.
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000118 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000119 if not self.IsEmpty():
120 cfgFile=open(self.file,'w')
121 self.write(cfgFile)
122 else:
123 if os.path.exists(self.file):
124 os.remove(self.file)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000125
126class IdleConf:
127 """
128 holds config parsers for all idle config files:
129 default config files
130 (idle install dir)/config-main.def
131 (idle install dir)/config-extensions.def
132 (idle install dir)/config-highlight.def
133 (idle install dir)/config-keys.def
134 user config files
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000135 (user home dir)/.idlerc/config-main.cfg
136 (user home dir)/.idlerc/config-extensions.cfg
137 (user home dir)/.idlerc/config-highlight.cfg
138 (user home dir)/.idlerc/config-keys.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000139 """
140 def __init__(self):
141 self.defaultCfg={}
142 self.userCfg={}
143 self.cfg={}
144 self.CreateConfigHandlers()
145 self.LoadCfgFiles()
146 #self.LoadCfg()
147
148 def CreateConfigHandlers(self):
149 """
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000150 set up a dictionary of config parsers for default and user
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000151 configurations respectively
152 """
153 #build idle install path
154 if __name__ != '__main__': # we were imported
155 idledir=os.path.dirname(__file__)
156 else: # we were exec'ed (for testing only)
157 idledir=os.path.abspath(sys.path[0])
158 #print idledir
159 try: #build user home path
160 userdir = os.environ['HOME'] #real home directory
161 except KeyError:
162 userdir = os.getcwd() #hack for os'es without real homedirs
163 userdir=os.path.join(userdir,'.idlerc')
164 #print userdir
165 if not os.path.exists(userdir):
166 os.mkdir(userdir)
167 configTypes=('main','extensions','highlight','keys')
168 defCfgFiles={}
169 usrCfgFiles={}
170 for cfgType in configTypes: #build config file names
171 defCfgFiles[cfgType]=os.path.join(idledir,'config-'+cfgType+'.def')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000172 usrCfgFiles[cfgType]=os.path.join(userdir,'config-'+cfgType+'.cfg')
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000173 for cfgType in configTypes: #create config parsers
174 self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
175 self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
176
Steven M. Gava2a63a072001-10-26 06:50:54 +0000177 def GetOption(self, configType, section, option, default=None, type=None):
Steven M. Gava429a86a2001-10-23 10:42:12 +0000178 """
179 Get an option value for given config type and given general
180 configuration section/option or return a default. If type is specified,
181 return as type. Firstly the user configuration is checked, with a
182 fallback to the default configuration, and a final 'catch all'
183 fallback to a useable passed-in default if the option isn't present in
184 either the user or the default configuration.
185 configType must be one of ('main','extensions','highlight','keys')
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000186 If a default is returned a warning is printed to stderr.
Steven M. Gava429a86a2001-10-23 10:42:12 +0000187 """
188 if self.userCfg[configType].has_option(section,option):
189 return self.userCfg[configType].Get(section, option, type=type)
190 elif self.defaultCfg[configType].has_option(section,option):
191 return self.defaultCfg[configType].Get(section, option, type=type)
192 else:
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000193 warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
194 ' problem retrieving configration option '+`option`+'\n'+
195 ' from section '+`section`+'.\n'+
196 ' returning default value: '+`default`+'\n')
197 sys.stderr.write(warning)
Steven M. Gava429a86a2001-10-23 10:42:12 +0000198 return default
199
Steven M. Gava2a63a072001-10-26 06:50:54 +0000200 def GetSectionList(self, configSet, configType):
201 """
202 Get a list of sections from either the user or default config for
203 the given config type.
204 configSet must be either 'user' or 'default'
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000205 configType must be one of ('main','extensions','highlight','keys')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000206 """
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000207 if not (configType in ('main','extensions','highlight','keys')):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000208 raise 'Invalid configType specified'
209 if configSet == 'user':
210 cfgParser=self.userCfg[configType]
211 elif configSet == 'default':
212 cfgParser=self.defaultCfg[configType]
213 else:
214 raise 'Invalid configSet specified'
Steven M. Gava2a63a072001-10-26 06:50:54 +0000215 return cfgParser.sections()
216
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000217 def GetHighlight(self, theme, element, fgBg=None):
218 """
219 return individual highlighting theme elements.
220 fgBg - string ('fg'or'bg') or None, if None return a dictionary
221 containing fg and bg colours (appropriate for passing to Tkinter in,
222 e.g., a tag_config call), otherwise fg or bg colour only as specified.
223 """
Steven M. Gava99300612001-11-04 07:03:08 +0000224 #get some fallback defaults
225 defaultFg=self.GetOption('highlight', theme, 'normal' + "-foreground",
226 default='#000000')
227 defaultBg=self.GetOption('highlight', theme, 'normal' + "-background",
228 default='#ffffff')
229 #try for requested element colours
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000230 fore = self.GetOption('highlight', theme, element + "-foreground")
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000231 back = None
232 if element == 'cursor': #there is no config value for cursor bg
233 back = None
234 else:
235 back = self.GetOption('highlight', theme, element + "-background")
Steven M. Gava99300612001-11-04 07:03:08 +0000236 #fall back if required
237 if not fore: fore=defaultFg
238 if not back: back=defaultBg
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000239 highlight={"foreground": fore,"background": back}
240 if not fgBg: #return dict of both colours
241 return highlight
242 else: #return specified colour only
243 if fgBg == 'fg':
244 return highlight["foreground"]
245 if fgBg == 'bg':
246 return highlight["background"]
247 else:
248 raise 'Invalid fgBg specified'
249
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000250 def GetThemeDict(self,type,themeName):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000251 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000252 type - string, 'default' or 'user' theme type
253 themeName - string, theme name
254 Returns a dictionary which holds {option:value} for each element
255 in the specified theme. Values are loaded over a set of ultimate last
256 fallback defaults to guarantee that all theme elements are present in
257 a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000258 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000259 if type == 'user':
260 cfgParser=self.userCfg['highlight']
261 elif type == 'default':
262 cfgParser=self.defaultCfg['highlight']
263 else:
264 raise 'Invalid theme type specified'
265 #foreground and background values are provded for each theme element
266 #(apart from cursor) even though all these values are not yet used
267 #by idle, to allow for their use in the future. Default values are
268 #generally black and white.
269 theme={ 'normal-foreground':'#000000',
270 'normal-background':'#ffffff',
271 'keyword-foreground':'#000000',
272 'keyword-background':'#ffffff',
273 'comment-foreground':'#000000',
274 'comment-background':'#ffffff',
275 'string-foreground':'#000000',
276 'string-background':'#ffffff',
277 'definition-foreground':'#000000',
278 'definition-background':'#ffffff',
279 'hilite-foreground':'#000000',
280 'hilite-background':'gray',
281 'break-foreground':'#ffffff',
282 'break-background':'#000000',
283 'hit-foreground':'#ffffff',
284 'hit-background':'#000000',
285 'error-foreground':'#ffffff',
286 'error-background':'#000000',
287 #cursor (only foreground can be set)
288 'cursor-foreground':'#000000',
289 #shell window
290 'stdout-foreground':'#000000',
291 'stdout-background':'#ffffff',
292 'stderr-foreground':'#000000',
293 'stderr-background':'#ffffff',
294 'console-foreground':'#000000',
295 'console-background':'#ffffff' }
296 for element in theme.keys():
297 colour=cfgParser.Get(type,themeName,element,default=theme[element])
298 theme[element]=colour
299 return theme
300
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000301 def CurrentTheme(self):
302 """
303 Returns the name of the currently active theme
304 """
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000305 return self.GetOption('main','Theme','name',default='')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000306
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000307 def CurrentKeys(self):
308 """
309 Returns the name of the currently active theme
310 """
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000311 return self.GetOption('main','Keys','name',default='')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000312
313 def GetExtensions(self, activeOnly=1):
314 """
315 Gets a list of all idle extensions declared in the config files.
316 activeOnly - boolean, if true only return active (enabled) extensions
317 """
Steven M. Gavac628a062002-01-19 10:33:21 +0000318 extns=self.RemoveKeyBindNames(
319 self.GetSectionList('default','extensions'))
320 userExtns=self.RemoveKeyBindNames(
321 self.GetSectionList('user','extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000322 for extn in userExtns:
323 if extn not in extns: #user has added own extension
324 extns.append(extn)
325 if activeOnly:
326 activeExtns=[]
327 for extn in extns:
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000328 if self.GetOption('extensions',extn,'enable',default=1,
329 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000330 #the extension is enabled
331 activeExtns.append(extn)
332 return activeExtns
333 else:
334 return extns
335
Steven M. Gavac628a062002-01-19 10:33:21 +0000336 def RemoveKeyBindNames(self,extnNameList):
337 #get rid of keybinding section names
338 names=extnNameList
339 kbNameIndicies=[]
340 for name in names:
341 if name.endswith('_bindings') or name.endswith('_cfgBindings'):
342 kbNameIndicies.append(names.index(name))
343 kbNameIndicies.sort()
344 kbNameIndicies.reverse()
345 for index in kbNameIndicies: #delete each keybinding section name
346 del(names[index])
347 return names
348
349 def GetExtensionKeys(self,extensionName):
350 """
351 returns a dictionary of the configurable keybindings for a particular
352 extension,as they exist in the dictionary returned by GetCurrentKeySet;
353 that is, where previously re-used bindings are disabled.
354 """
355 keysName=extensionName+'_cfgBindings'
356 activeKeys=self.GetCurrentKeySet()
357 extKeys={}
358 if self.defaultCfg['extensions'].has_section(keysName):
359 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
360 for eventName in eventNames:
361 event='<<'+eventName+'>>'
362 binding=activeKeys[event]
363 extKeys[event]=binding
364 return extKeys
365
366 def __GetRawExtensionKeys(self,extensionName):
367 """
368 returns a dictionary of the configurable keybindings for a particular
369 extension, as defined in the configuration files, or an empty dictionary
370 if no bindings are found
371 """
372 keysName=extensionName+'_cfgBindings'
373 extKeys={}
374 if self.defaultCfg['extensions'].has_section(keysName):
375 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
376 for eventName in eventNames:
377 binding=self.GetOption('extensions',keysName,
378 eventName,default='').split()
379 event='<<'+eventName+'>>'
380 extKeys[event]=binding
381 return extKeys
382
383 def GetExtensionBindings(self,extensionName):
384 """
385 Returns a dictionary of all the event bindings for a particular
386 extension. The configurable keybindings are returned as they exist in
387 the dictionary returned by GetCurrentKeySet; that is, where re-used
388 keybindings are disabled.
389 """
390 bindsName=extensionName+'_bindings'
391 extBinds=self.GetExtensionKeys(extensionName)
392 #add the non-configurable bindings
393 if self.defaultCfg['extensions'].has_section(bindsName):
394 eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
395 for eventName in eventNames:
396 binding=self.GetOption('extensions',bindsName,
397 eventName,default='').split()
398 event='<<'+eventName+'>>'
399 extBinds[event]=binding
400
401 return extBinds
402
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000403 def GetKeyBinding(self, keySetName, eventStr):
404 """
405 returns the keybinding for a specific event.
406 keySetName - string, name of key binding set
407 eventStr - string, the virtual event we want the binding for,
408 represented as a string, eg. '<<event>>'
409 """
410 eventName=eventStr[2:-2] #trim off the angle brackets
411 binding=self.GetOption('keys',keySetName,eventName,default='').split()
412 return binding
413
Steven M. Gavac628a062002-01-19 10:33:21 +0000414 def GetCurrentKeySet(self):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000415 return self.GetKeySet(self.CurrentKeys())
416
417 def GetKeySet(self,keySetName):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000418 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000419 Returns a dictionary of: all requested core keybindings, plus the
Steven M. Gavac628a062002-01-19 10:33:21 +0000420 keybindings for all currently active extensions. If a binding defined
421 in an extension is already in use, that binding is disabled.
422 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000423 keySet=self.GetCoreKeys(keySetName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000424 activeExtns=self.GetExtensions(activeOnly=1)
425 for extn in activeExtns:
426 extKeys=self.__GetRawExtensionKeys(extn)
427 if extKeys: #the extension defines keybindings
428 for event in extKeys.keys():
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000429 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000430 #the binding is already in use
431 extKeys[event]='' #disable this binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000432 keySet[event]=extKeys[event] #add binding
433 return keySet
434
Steven M. Gavac628a062002-01-19 10:33:21 +0000435 def GetCoreKeys(self, keySetName=None):
436 """
437 returns the requested set of core keybindings, with fallbacks if
438 required.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000439 Keybindings loaded from the config file(s) are loaded _over_ these
440 defaults, so if there is a problem getting any core binding there will
441 be an 'ultimate last resort fallback' to the CUA-ish bindings
442 defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000443 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000444 keyBindings={
445 '<<Copy>>': ['<Control-c>', '<Control-C>'],
446 '<<Cut>>': ['<Control-x>', '<Control-X>'],
447 '<<Paste>>': ['<Control-v>', '<Control-V>'],
448 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
449 '<<center-insert>>': ['<Control-l>'],
450 '<<close-all-windows>>': ['<Control-q>'],
451 '<<close-window>>': ['<Alt-F4>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000452 '<<end-of-file>>': ['<Control-d>'],
453 '<<python-docs>>': ['<F1>'],
454 '<<python-context-help>>': ['<Shift-F1>'],
455 '<<history-next>>': ['<Alt-n>'],
456 '<<history-previous>>': ['<Alt-p>'],
457 '<<interrupt-execution>>': ['<Control-c>'],
458 '<<open-class-browser>>': ['<Alt-c>'],
459 '<<open-module>>': ['<Alt-m>'],
460 '<<open-new-window>>': ['<Control-n>'],
461 '<<open-window-from-file>>': ['<Control-o>'],
462 '<<plain-newline-and-indent>>': ['<Control-j>'],
463 '<<redo>>': ['<Control-y>'],
464 '<<remove-selection>>': ['<Escape>'],
465 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
466 '<<save-window-as-file>>': ['<Alt-s>'],
467 '<<save-window>>': ['<Control-s>'],
468 '<<select-all>>': ['<Alt-a>'],
469 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000470 '<<undo>>': ['<Control-z>'],
471 '<<find-again>>': ['<Control-g>', '<F3>'],
472 '<<find-in-files>>': ['<Alt-F3>'],
473 '<<find-selection>>': ['<Control-F3>'],
474 '<<find>>': ['<Control-f>'],
475 '<<replace>>': ['<Control-h>'],
476 '<<goto-line>>': ['<Alt-g>'] }
477
Steven M. Gava17d01542001-12-03 00:37:28 +0000478 if keySetName:
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000479 for event in keyBindings.keys():
480 binding=self.GetKeyBinding(keySetName,event)
481 if binding: #otherwise will keep default
482 keyBindings[event]=binding
Steven M. Gava17d01542001-12-03 00:37:28 +0000483
484 return keyBindings
485
Steven M. Gava2a63a072001-10-26 06:50:54 +0000486
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000487 def LoadCfgFiles(self):
488 """
489 load all configuration files.
490 """
491 for key in self.defaultCfg.keys():
492 self.defaultCfg[key].Load()
493 self.userCfg[key].Load() #same keys
494
495 def SaveUserCfgFiles(self):
496 """
497 write all loaded user configuration files back to disk
498 """
499 for key in self.userCfg.keys():
500 self.userCfg[key].Save()
501
502idleConf=IdleConf()
503
504### module test
505if __name__ == '__main__':
506 def dumpCfg(cfg):
507 print '\n',cfg,'\n'
508 for key in cfg.keys():
509 sections=cfg[key].sections()
510 print key
511 print sections
512 for section in sections:
513 options=cfg[key].options(section)
514 print section
515 print options
516 for option in options:
517 print option, '=', cfg[key].Get(section,option)
518 dumpCfg(idleConf.defaultCfg)
519 dumpCfg(idleConf.userCfg)
520 print idleConf.userCfg['main'].Get('Theme','name')
521 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')