blob: ed37f11a9cb8e02d867329af820df36f19bd54f2 [file] [log] [blame]
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001"""idlelib.config -- Manage IDLE configuration information.
Steven M. Gavac5976402002-01-04 03:06:08 +00002
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05003The comments at the beginning of config-main.def describe the
4configuration files and the design implemented to update user
5configuration information. In particular, user configuration choices
6which duplicate the defaults will be removed from the user's
7configuration files, and if a user file becomes empty, it will be
8deleted.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00009
terryjreedy296dc492017-06-21 21:43:47 -040010The configuration database maps options to values. Conceptually, the
Terry Jan Reedyf46b7822016-11-07 17:15:01 -050011database keys are tuples (config-type, section, item). As implemented,
12there are separate dicts for default and user values. Each has
13config-type keys 'main', 'extensions', 'highlight', and 'keys'. The
14value for each key is a ConfigParser instance that maps section and item
15to values. For 'main' and 'extenstons', user values override
16default values. For 'highlight' and 'keys', user sections augment the
17default sections (and must, therefore, have distinct names).
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000018
19Throughout this module there is an emphasis on returning useable defaults
20when a problem occurs in returning a requested configuration value back to
21idle. This is to allow IDLE to continue to function in spite of errors in
22the retrieval of config information. When a default is returned instead of
23a requested config value, a message is printed to stderr to aid in
24configuration problem notification and resolution.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000025"""
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040026# TODOs added Oct 2014, tjr
27
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040028from configparser import ConfigParser
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000029import os
30import sys
Guido van Rossum36e0a922007-07-20 04:05:57 +000031
terryjreedyc6696fe2017-06-11 05:53:46 -040032from tkinter.font import Font
Steven M. Gavac11ccf32001-09-24 09:43:17 +000033
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000034class InvalidConfigType(Exception): pass
35class InvalidConfigSet(Exception): pass
36class InvalidFgBg(Exception): pass
37class InvalidTheme(Exception): pass
38
Steven M. Gavac11ccf32001-09-24 09:43:17 +000039class IdleConfParser(ConfigParser):
40 """
41 A ConfigParser specialised for idle configuration file handling
42 """
43 def __init__(self, cfgFile, cfgDefaults=None):
44 """
45 cfgFile - string, fully specified configuration file name
46 """
terryjreedyedc03422017-07-07 16:37:39 -040047 self.file = cfgFile # This is currently '' when testing.
Serhiy Storchaka89953002013-02-07 15:24:36 +020048 ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000049
Thomas Wouterscf297e42007-02-23 15:07:44 +000050 def Get(self, section, option, type=None, default=None, raw=False):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000051 """
52 Get an option value for given section/option or return default.
53 If type is specified, return as type.
54 """
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040055 # TODO Use default as fallback, at least if not None
56 # Should also print Warning(file, section, option).
57 # Currently may raise ValueError
Thomas Wouterscf297e42007-02-23 15:07:44 +000058 if not self.has_option(section, option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000059 return default
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040060 if type == 'bool':
Thomas Wouterscf297e42007-02-23 15:07:44 +000061 return self.getboolean(section, option)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040062 elif type == 'int':
Thomas Wouterscf297e42007-02-23 15:07:44 +000063 return self.getint(section, option)
64 else:
65 return self.get(section, option, raw=raw)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000066
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040067 def GetOptionList(self, section):
68 "Return a list of options for given section, else []."
Steven M. Gava085eb1b2002-02-05 04:52:32 +000069 if self.has_section(section):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000070 return self.options(section)
71 else: #return a default value
72 return []
73
Steven M. Gavac11ccf32001-09-24 09:43:17 +000074 def Load(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040075 "Load the configuration file from disk."
terryjreedyedc03422017-07-07 16:37:39 -040076 if self.file:
77 self.read(self.file)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000078
Steven M. Gavac11ccf32001-09-24 09:43:17 +000079class IdleUserConfParser(IdleConfParser):
80 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000081 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000082 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000083
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040084 def AddSection(self, section):
85 "If section doesn't exist, add it."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000086 if not self.has_section(section):
87 self.add_section(section)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000088
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000089 def RemoveEmptySections(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040090 "Remove any sections that have no options."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000091 for section in self.sections():
92 if not self.GetOptionList(section):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000093 self.remove_section(section)
94
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000095 def IsEmpty(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040096 "Return True if no sections after removing empty sections."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000097 self.RemoveEmptySections()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040098 return not self.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000099
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400100 def RemoveOption(self, section, option):
101 """Return True if option is removed from section, else False.
102
103 False if either section does not exist or did not have option.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000104 """
105 if self.has_section(section):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400106 return self.remove_option(section, option)
107 return False
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000108
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400109 def SetOption(self, section, option, value):
110 """Return True if option is added or changed to value, else False.
111
112 Add section if required. False means option already had value.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000113 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400114 if self.has_option(section, option):
115 if self.get(section, option) == value:
116 return False
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000117 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400118 self.set(section, option, value)
119 return True
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000120 else:
121 if not self.has_section(section):
122 self.add_section(section)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400123 self.set(section, option, value)
124 return True
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000125
Steven M. Gavab77d3432002-03-02 07:16:21 +0000126 def RemoveFile(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400127 "Remove user config file self.file from disk if it exists."
Steven M. Gavab77d3432002-03-02 07:16:21 +0000128 if os.path.exists(self.file):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000129 os.remove(self.file)
130
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000131 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000132 """Update user configuration file.
133
terryjreedyedc03422017-07-07 16:37:39 -0400134 If self not empty after removing empty sections, write the file
135 to disk. Otherwise, remove the file from disk if it exists.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000136
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000137 """
terryjreedyedc03422017-07-07 16:37:39 -0400138 fname = self.file
139 if fname:
140 if not self.IsEmpty():
141 try:
142 cfgFile = open(fname, 'w')
143 except OSError:
144 os.unlink(fname)
145 cfgFile = open(fname, 'w')
146 with cfgFile:
147 self.write(cfgFile)
148 else:
149 self.RemoveFile()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000150
151class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400152 """Hold config parsers for all idle config files in singleton instance.
153
154 Default config files, self.defaultCfg --
155 for config_type in self.config_types:
156 (idle install dir)/config-{config-type}.def
157
158 User config files, self.userCfg --
159 for config_type in self.config_types:
160 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000161 """
162 def __init__(self):
terryjreedyedc03422017-07-07 16:37:39 -0400163 self.config_types = ('main', 'highlight', 'keys', 'extensions')
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400164 self.defaultCfg = {}
165 self.userCfg = {}
166 self.cfg = {} # TODO use to select userCfg vs defaultCfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000167 self.CreateConfigHandlers()
168 self.LoadCfgFiles()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400169
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000170
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000171 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400172 "Populate default and user config parser dictionaries."
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000173 #build idle install path
174 if __name__ != '__main__': # we were imported
terryjreedy552f2662017-07-07 22:47:37 -0400175 idleDir = os.path.dirname(__file__)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000176 else: # we were exec'ed (for testing only)
terryjreedy552f2662017-07-07 22:47:37 -0400177 idleDir = os.path.abspath(sys.path[0])
178 self.userdir = userDir = self.GetUserCfgDir()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400179
180 defCfgFiles = {}
181 usrCfgFiles = {}
182 # TODO eliminate these temporaries by combining loops
183 for cfgType in self.config_types: #build config file names
184 defCfgFiles[cfgType] = os.path.join(
185 idleDir, 'config-' + cfgType + '.def')
186 usrCfgFiles[cfgType] = os.path.join(
187 userDir, 'config-' + cfgType + '.cfg')
188 for cfgType in self.config_types: #create config parsers
189 self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
190 self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000191
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000192 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400193 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000194
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400195 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000196 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000197 cfgDir = '.idlerc'
198 userDir = os.path.expanduser('~')
199 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000200 if not os.path.exists(userDir):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400201 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
202 userDir + ',\n but the path does not exist.')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000203 try:
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400204 print(warn, file=sys.stderr)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200205 except OSError:
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000206 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000207 userDir = '~'
208 if userDir == "~": # still no path to home!
209 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
210 userDir = os.getcwd()
211 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000212 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000213 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000214 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200215 except OSError:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400216 warn = ('\n Warning: unable to create user config directory\n' +
217 userDir + '\n Check path and permissions.\n Exiting!\n')
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400218 print(warn, file=sys.stderr)
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000219 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400220 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000221 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000222
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000223 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000224 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400225 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000226
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400227 If type is not None, return a value of that type. Also pass raw
228 to the config parser. First try to return a valid value
229 (including type) from a user configuration. If that fails, try
230 the default configuration. If that fails, return default, with a
231 default of None.
232
233 Warn if either user or default configurations have an invalid value.
234 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000235 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200236 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400237 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200238 return self.userCfg[configType].Get(section, option,
239 type=type, raw=raw)
240 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400241 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200242 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400243 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200244 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400245 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400246 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200247 try:
248 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400249 return self.defaultCfg[configType].Get(
250 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200251 except ValueError:
252 pass
253 #returning default, print warning
254 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400255 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200256 ' problem retrieving configuration option %r\n'
257 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400258 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200259 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400260 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200261 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000262
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000263 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400264 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000265 self.userCfg[configType].SetOption(section, option, value)
266
Steven M. Gava2a63a072001-10-26 06:50:54 +0000267 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400268 """Return sections for configSet configType configuration.
269
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000270 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400271 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000272 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400273 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000274 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000275 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400276 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000277 elif configSet == 'default':
278 cfgParser=self.defaultCfg[configType]
279 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000280 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000281 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000282
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000283 def GetHighlight(self, theme, element, fgBg=None):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400284 """Return individual theme element highlight color(s).
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400285
Terry Jan Reedy86757992014-10-09 18:44:32 -0400286 fgBg - string ('fg' or 'bg') or None.
287 If None, return a dictionary containing fg and bg colors with
288 keys 'foreground' and 'background'. Otherwise, only return
289 fg or bg color, as specified. Colors are intended to be
290 appropriate for passing to Tkinter in, e.g., a tag_config call).
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000291 """
Steven M. Gava9f25e672002-02-11 02:51:18 +0000292 if self.defaultCfg['highlight'].has_section(theme):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400293 themeDict = self.GetThemeDict('default', theme)
Steven M. Gava9f25e672002-02-11 02:51:18 +0000294 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400295 themeDict = self.GetThemeDict('user', theme)
296 fore = themeDict[element + '-foreground']
Terry Jan Reedy86757992014-10-09 18:44:32 -0400297 if element == 'cursor': # There is no config value for cursor bg
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400298 back = themeDict['normal-background']
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000299 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400300 back = themeDict[element + '-background']
301 highlight = {"foreground": fore, "background": back}
Terry Jan Reedy86757992014-10-09 18:44:32 -0400302 if not fgBg: # Return dict of both colors
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000303 return highlight
Terry Jan Reedy86757992014-10-09 18:44:32 -0400304 else: # Return specified color only
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000305 if fgBg == 'fg':
306 return highlight["foreground"]
307 if fgBg == 'bg':
308 return highlight["background"]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000309 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000310 raise InvalidFgBg('Invalid fgBg specified')
Steven M. Gava9f25e672002-02-11 02:51:18 +0000311
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400312 def GetThemeDict(self, type, themeName):
313 """Return {option:value} dict for elements in themeName.
314
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000315 type - string, 'default' or 'user' theme type
316 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400317 Values are loaded over ultimate fallback defaults to guarantee
318 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000319 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000320 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400321 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000322 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400323 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000324 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000325 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400326 # Provide foreground and background colors for each theme
327 # element (other than cursor) even though some values are not
328 # yet used by idle, to allow for their use in the future.
329 # Default values are generally black and white.
330 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400331 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000332 'normal-background':'#ffffff',
333 'keyword-foreground':'#000000',
334 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000335 'builtin-foreground':'#000000',
336 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000337 'comment-foreground':'#000000',
338 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000339 'string-foreground':'#000000',
340 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000341 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000342 'definition-background':'#ffffff',
343 'hilite-foreground':'#000000',
344 'hilite-background':'gray',
345 'break-foreground':'#ffffff',
346 'break-background':'#000000',
347 'hit-foreground':'#ffffff',
348 'hit-background':'#000000',
349 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000350 'error-background':'#000000',
351 #cursor (only foreground can be set)
352 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000353 #shell window
354 'stdout-foreground':'#000000',
355 'stdout-background':'#ffffff',
356 'stderr-foreground':'#000000',
357 'stderr-background':'#ffffff',
358 'console-foreground':'#000000',
359 'console-background':'#ffffff' }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000360 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400361 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400362 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400363 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000364 ' -\n problem retrieving theme element %r'
365 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400366 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000367 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400368 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400369 theme[element] = cfgParser.Get(
370 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000371 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000372
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000373 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400374 "Return the name of the currently active text color theme."
375 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500376
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400377 def CurrentKeys(self):
378 """Return the name of the currently active key set."""
379 return self.current_colors_and_keys('Keys')
380
381 def current_colors_and_keys(self, section):
382 """Return the currently active name for Theme or Keys section.
383
384 idlelib.config-main.def ('default') includes these sections
385
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500386 [Theme]
387 default= 1
388 name= IDLE Classic
389 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500390
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400391 [Keys]
392 default= 1
393 name=
394 name2=
395
396 Item 'name2', is used for built-in ('default') themes and keys
397 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
398 because setting 'name' to a builtin not defined in older IDLEs
399 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500400 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400401 When default = True, 'name2' takes precedence over 'name',
402 while older IDLEs will just use name. When default = False,
403 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500404 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400405 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400406 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500407 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500409 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400410 name = self.GetOption('main', section, 'name2', default='')
411 if not name:
412 name = self.GetOption('main', section, 'name', default='')
413 if name:
414 source = self.defaultCfg if default else self.userCfg
415 if source[cfgname].has_section(name):
416 return name
417 return "IDLE Classic" if section == 'Theme' else self.default_keys()
418
419 @staticmethod
420 def default_keys():
421 if sys.platform[:3] == 'win':
422 return 'IDLE Classic Windows'
423 elif sys.platform == 'darwin':
424 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500425 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400426 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000427
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400428 def GetExtensions(self, active_only=True,
429 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400430 """Return extensions in default and user config-extensions files.
431
432 If active_only True, only return active (enabled) extensions
433 and optionally only editor or shell extensions.
434 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000435 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400436 extns = self.RemoveKeyBindNames(
437 self.GetSectionList('default', 'extensions'))
438 userExtns = self.RemoveKeyBindNames(
439 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000440 for extn in userExtns:
441 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000442 extns.append(extn)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000443 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400444 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000445 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000446 if self.GetOption('extensions', extn, 'enable', default=True,
447 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000448 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400449 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000450 if editor_only:
451 option = "enable_editor"
452 else:
453 option = "enable_shell"
454 if self.GetOption('extensions', extn,option,
455 default=True, type='bool',
456 warn_on_default=False):
457 activeExtns.append(extn)
458 else:
459 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000460 return activeExtns
461 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000462 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000463
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400464 def RemoveKeyBindNames(self, extnNameList):
465 "Return extnNameList with keybinding section names removed."
466 # TODO Easier to return filtered copy with list comp
467 names = extnNameList
468 kbNameIndicies = []
Steven M. Gavac628a062002-01-19 10:33:21 +0000469 for name in names:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000470 if name.endswith(('_bindings', '_cfgBindings')):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000471 kbNameIndicies.append(names.index(name))
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400472 kbNameIndicies.sort(reverse=True)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000473 for index in kbNameIndicies: #delete each keybinding section name
Steven M. Gavac628a062002-01-19 10:33:21 +0000474 del(names[index])
475 return names
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000476
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400477 def GetExtnNameForEvent(self, virtualEvent):
478 """Return the name of the extension binding virtualEvent, or None.
479
480 virtualEvent - string, name of the virtual event to test for,
481 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000482 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400483 extName = None
484 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000485 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000486 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000487 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400488 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000489 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000490
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400491 def GetExtensionKeys(self, extensionName):
492 """Return dict: {configurable extensionName event : active keybinding}.
493
494 Events come from default config extension_cfgBindings section.
495 Keybindings come from GetCurrentKeySet() active key dict,
496 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000497 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400498 keysName = extensionName + '_cfgBindings'
499 activeKeys = self.GetCurrentKeySet()
500 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000501 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400502 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000503 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400504 event = '<<' + eventName + '>>'
505 binding = activeKeys[event]
506 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000507 return extKeys
508
Steven M. Gavac628a062002-01-19 10:33:21 +0000509 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400510 """Return dict {configurable extensionName event : keybinding list}.
511
512 Events come from default config extension_cfgBindings section.
513 Keybindings list come from the splitting of GetOption, which
514 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000515 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400516 keysName = extensionName+'_cfgBindings'
517 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000518 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400519 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000520 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400521 binding = self.GetOption(
522 'extensions', keysName, eventName, default='').split()
523 event = '<<' + eventName + '>>'
524 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000525 return extKeys
526
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400527 def GetExtensionBindings(self, extensionName):
528 """Return dict {extensionName event : active or defined keybinding}.
529
530 Augment self.GetExtensionKeys(extensionName) with mapping of non-
531 configurable events (from default config) to GetOption splits,
532 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000533 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400534 bindsName = extensionName + '_bindings'
535 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000536 #add the non-configurable bindings
537 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400538 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000539 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400540 binding = self.GetOption(
541 'extensions', bindsName, eventName, default='').split()
542 event = '<<' + eventName + '>>'
543 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000544
545 return extBinds
546
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000547 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400548 """Return the keybinding list for keySetName eventStr.
549
550 keySetName - name of key binding set (config-keys section).
551 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000552 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400553 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400554 binding = self.GetOption('keys', keySetName, eventName, default='',
555 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000556 return binding
557
Steven M. Gavac628a062002-01-19 10:33:21 +0000558 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400559 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000560 result = self.GetKeySet(self.CurrentKeys())
561
Ned Deilyb7601672014-03-27 20:49:14 -0700562 if sys.platform == "darwin":
563 # OS X Tk variants do not support the "Alt" keyboard modifier.
564 # So replace all keybingings that use "Alt" with ones that
565 # use the "Option" keyboard modifier.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400566 # TODO (Ned?): the "Option" modifier does not work properly for
Ned Deilyb7601672014-03-27 20:49:14 -0700567 # Cocoa Tk and XQuartz Tk so we should not use it
568 # in default OS X KeySets.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000569 for k, v in result.items():
570 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
571 if v != v2:
572 result[k] = v2
573
574 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000575
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400576 def GetKeySet(self, keySetName):
577 """Return event-key dict for keySetName core plus active extensions.
578
579 If a binding defined in an extension is already in use, the
580 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000581 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400582 keySet = self.GetCoreKeys(keySetName)
583 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000584 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400585 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000586 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000587 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000588 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000589 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400590 extKeys[event] = '' #disable this binding
591 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000592 return keySet
593
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400594 def IsCoreBinding(self, virtualEvent):
595 """Return True if the virtual event is one of the core idle key events.
596
597 virtualEvent - string, name of the virtual event to test for,
598 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000599 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000600 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000601
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400602# TODO make keyBindins a file or class attribute used for test above
603# and copied in function below
604
Steven M. Gavac628a062002-01-19 10:33:21 +0000605 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400606 """Return dict of core virtual-key keybindings for keySetName.
607
608 The default keySetName None corresponds to the keyBindings base
609 dict. If keySetName is not None, bindings from the config
610 file(s) are loaded _over_ these defaults, so if there is a
611 problem getting any core binding there will be an 'ultimate last
612 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000613 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000614 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000615 '<<copy>>': ['<Control-c>', '<Control-C>'],
616 '<<cut>>': ['<Control-x>', '<Control-X>'],
617 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000618 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
619 '<<center-insert>>': ['<Control-l>'],
620 '<<close-all-windows>>': ['<Control-q>'],
621 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000622 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000623 '<<end-of-file>>': ['<Control-d>'],
624 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000625 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000626 '<<history-next>>': ['<Alt-n>'],
627 '<<history-previous>>': ['<Alt-p>'],
628 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000629 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000630 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000631 '<<open-class-browser>>': ['<Alt-c>'],
632 '<<open-module>>': ['<Alt-m>'],
633 '<<open-new-window>>': ['<Control-n>'],
634 '<<open-window-from-file>>': ['<Control-o>'],
635 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000636 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000637 '<<redo>>': ['<Control-y>'],
638 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000639 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000640 '<<save-window-as-file>>': ['<Alt-s>'],
641 '<<save-window>>': ['<Control-s>'],
642 '<<select-all>>': ['<Alt-a>'],
643 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000644 '<<undo>>': ['<Control-z>'],
645 '<<find-again>>': ['<Control-g>', '<F3>'],
646 '<<find-in-files>>': ['<Alt-F3>'],
647 '<<find-selection>>': ['<Control-F3>'],
648 '<<find>>': ['<Control-f>'],
649 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000650 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000651 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300652 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000653 '<<smart-indent>>': ['<Key-Tab>'],
654 '<<indent-region>>': ['<Control-Key-bracketright>'],
655 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
656 '<<comment-region>>': ['<Alt-Key-3>'],
657 '<<uncomment-region>>': ['<Alt-Key-4>'],
658 '<<tabify-region>>': ['<Alt-Key-5>'],
659 '<<untabify-region>>': ['<Alt-Key-6>'],
660 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000661 '<<change-indentwidth>>': ['<Alt-Key-u>'],
662 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
663 '<<del-word-right>>': ['<Control-Key-Delete>']
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000664 }
Steven M. Gava17d01542001-12-03 00:37:28 +0000665 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400666 if not (self.userCfg['keys'].has_section(keySetName) or
667 self.defaultCfg['keys'].has_section(keySetName)):
668 warning = (
669 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
670 ' key set %r is not defined, using default bindings.' %
671 (keySetName,)
672 )
673 _warn(warning, 'keys', keySetName)
674 else:
675 for event in keyBindings:
676 binding = self.GetKeyBinding(keySetName, event)
677 if binding:
678 keyBindings[event] = binding
679 else: #we are going to return a default, print warning
680 warning = (
681 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
682 ' problem retrieving key binding for event %r\n'
683 ' from key set %r.\n'
684 ' returning default value: %r' %
685 (event, keySetName, keyBindings[event])
686 )
687 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000688 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000689
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400690 def GetExtraHelpSourceList(self, configSet):
691 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000692
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000693 Valid configSets are 'user' or 'default'. Return a list of tuples of
694 the form (menu_item , path_to_help_file , option), or return the empty
695 list. 'option' is the sequence number of the help resource. 'option'
696 values determine the position of the menu items on the Help menu,
697 therefore the returned list must be sorted by 'option'.
698
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000699 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400700 helpSources = []
701 if configSet == 'user':
702 cfgParser = self.userCfg['main']
703 elif configSet == 'default':
704 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000705 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000706 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000707 options=cfgParser.GetOptionList('HelpFiles')
708 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400709 value=cfgParser.Get('HelpFiles', option, default=';')
710 if value.find(';') == -1: #malformed config entry with no ';'
711 menuItem = '' #make these empty
712 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000713 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000714 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000715 menuItem=value[0].strip()
716 helpPath=value[1].strip()
717 if menuItem and helpPath: #neither are empty strings
718 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000719 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000720 return helpSources
721
722 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400723 """Return a list of the details of all additional help sources.
724
725 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000726 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400727 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000728 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000729 return allHelpSources
730
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400731 def GetFont(self, root, configType, section):
732 """Retrieve a font from configuration (font, font-size, font-bold)
733 Intercept the special value 'TkFixedFont' and substitute
734 the actual font, factoring in some tweaks if needed for
735 appearance sakes.
736
737 The 'root' parameter can normally be any valid Tkinter widget.
738
739 Return a tuple (family, size, weight) suitable for passing
740 to tkinter.Font
741 """
742 family = self.GetOption(configType, section, 'font', default='courier')
743 size = self.GetOption(configType, section, 'font-size', type='int',
744 default='10')
745 bold = self.GetOption(configType, section, 'font-bold', default=0,
746 type='bool')
747 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400748 f = Font(name='TkFixedFont', exists=True, root=root)
749 actualFont = Font.actual(f)
750 family = actualFont['family']
751 size = actualFont['size']
752 if size <= 0:
753 size = 10 # if font in pixels, ignore actual size
754 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400755 return (family, size, 'bold' if bold else 'normal')
756
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000757 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400758 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000759 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000760 self.defaultCfg[key].Load()
761 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000762
763 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400764 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000765 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000766 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000767
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000768
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400769idleConf = IdleConf()
770
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400771_warned = set()
772def _warn(msg, *key):
773 key = (msg,) + key
774 if key not in _warned:
775 try:
776 print(msg, file=sys.stderr)
777 except OSError:
778 pass
779 _warned.add(key)
780
781
terryjreedyedc03422017-07-07 16:37:39 -0400782class ConfigChanges(dict):
783 """Manage a user's proposed configuration option changes.
784
785 Names used across multiple methods:
786 page -- one of the 4 top-level dicts representing a
787 .idlerc/config-x.cfg file.
788 config_type -- name of a page.
789 section -- a section within a page/file.
790 option -- name of an option within a section.
791 value -- value for the option.
792
793 Methods
794 add_option: Add option and value to changes.
795 save_option: Save option and value to config parser.
796 save_all: Save all the changes to the config parser and file.
797 delete_section: Delete section if it exists.
798 clear: Clear all changes by clearing each page.
799 """
800 def __init__(self):
801 "Create a page for each configuration file"
802 self.pages = [] # List of unhashable dicts.
803 for config_type in idleConf.config_types:
804 self[config_type] = {}
805 self.pages.append(self[config_type])
806
807 def add_option(self, config_type, section, item, value):
808 "Add item/value pair for config_type and section."
809 page = self[config_type]
810 value = str(value) # Make sure we use a string.
811 if section not in page:
812 page[section] = {}
813 page[section][item] = value
814
815 @staticmethod
816 def save_option(config_type, section, item, value):
817 """Return True if the configuration value was added or changed.
818
819 Helper for save_all.
820 """
821 if idleConf.defaultCfg[config_type].has_option(section, item):
822 if idleConf.defaultCfg[config_type].Get(section, item) == value:
823 # The setting equals a default setting, remove it from user cfg.
824 return idleConf.userCfg[config_type].RemoveOption(section, item)
825 # If we got here, set the option.
826 return idleConf.userCfg[config_type].SetOption(section, item, value)
827
828 def save_all(self):
829 """Save configuration changes to the user config file.
830
831 Then clear self in preparation for additional changes.
832 """
833 idleConf.userCfg['main'].Save()
834 for config_type in self:
835 cfg_type_changed = False
836 page = self[config_type]
837 for section in page:
838 if section == 'HelpFiles': # Remove it for replacement.
839 idleConf.userCfg['main'].remove_section('HelpFiles')
840 cfg_type_changed = True
841 for item, value in page[section].items():
842 if self.save_option(config_type, section, item, value):
843 cfg_type_changed = True
844 if cfg_type_changed:
845 idleConf.userCfg[config_type].Save()
846 for config_type in ['keys', 'highlight']:
847 # Save these even if unchanged!
848 idleConf.userCfg[config_type].Save()
849 self.clear()
850 # ConfigDialog caller must add the following call
851 # self.save_all_changed_extensions() # Uses a different mechanism.
852
853 def delete_section(self, config_type, section):
854 """Delete a section from self, userCfg, and file.
855
856 Used to delete custom themes and keysets.
857 """
858 if section in self[config_type]:
859 del self[config_type][section]
860 configpage = idleConf.userCfg[config_type]
861 configpage.remove_section(section)
862 configpage.Save()
863
864 def clear(self):
865 """Clear all 4 pages.
866
867 Called in save_all after saving to idleConf.
868 XXX Mark window *title* when there are changes; unmark here.
869 """
870 for page in self.pages:
871 page.clear()
872
873
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400874# TODO Revise test output, write expanded unittest
terryjreedyedc03422017-07-07 16:37:39 -0400875def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400876 from zlib import crc32
877 line, crc = 0, 0
878
879 def sprint(obj):
880 global line, crc
881 txt = str(obj)
882 line += 1
883 crc = crc32(txt.encode(encoding='utf-8'), crc)
884 print(txt)
terryjreedyedc03422017-07-07 16:37:39 -0400885 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400886
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000887 def dumpCfg(cfg):
terryjreedyedc03422017-07-07 16:37:39 -0400888 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400889 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400890 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400891 sprint(key)
892 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000893 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400894 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400895 sprint(section)
896 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000897 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400898 sprint(option + ' = ' + cfg[key].Get(section, option))
899
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000900 dumpCfg(idleConf.defaultCfg)
901 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400902 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedyedc03422017-07-07 16:37:39 -0400903
904if __name__ == '__main__':
905 import unittest
906 unittest.main('idlelib.idle_test.test_config',
907 verbosity=2, exit=False)
908 #_dump()