blob: 63d9a44234560b7502a12b267d0b58452b6c69e4 [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
KunYuChenf3e82092017-06-21 12:30:45 +080010The 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
Victor Stinnerd6debb22017-03-27 16:05:26 +020032from tkinter.font import Font
Louie Luf776eb02017-07-19 05:17:56 +080033import idlelib
Steven M. Gavac11ccf32001-09-24 09:43:17 +000034
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000035class InvalidConfigType(Exception): pass
36class InvalidConfigSet(Exception): pass
37class InvalidFgBg(Exception): pass
38class InvalidTheme(Exception): pass
39
Steven M. Gavac11ccf32001-09-24 09:43:17 +000040class IdleConfParser(ConfigParser):
41 """
42 A ConfigParser specialised for idle configuration file handling
43 """
44 def __init__(self, cfgFile, cfgDefaults=None):
45 """
46 cfgFile - string, fully specified configuration file name
47 """
terryjreedy349abd92017-07-07 16:00:57 -040048 self.file = cfgFile # This is currently '' when testing.
Serhiy Storchaka89953002013-02-07 15:24:36 +020049 ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000050
Thomas Wouterscf297e42007-02-23 15:07:44 +000051 def Get(self, section, option, type=None, default=None, raw=False):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000052 """
53 Get an option value for given section/option or return default.
54 If type is specified, return as type.
55 """
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040056 # TODO Use default as fallback, at least if not None
57 # Should also print Warning(file, section, option).
58 # Currently may raise ValueError
Thomas Wouterscf297e42007-02-23 15:07:44 +000059 if not self.has_option(section, option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000060 return default
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040061 if type == 'bool':
Thomas Wouterscf297e42007-02-23 15:07:44 +000062 return self.getboolean(section, option)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040063 elif type == 'int':
Thomas Wouterscf297e42007-02-23 15:07:44 +000064 return self.getint(section, option)
65 else:
66 return self.get(section, option, raw=raw)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000067
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040068 def GetOptionList(self, section):
69 "Return a list of options for given section, else []."
Steven M. Gava085eb1b2002-02-05 04:52:32 +000070 if self.has_section(section):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000071 return self.options(section)
72 else: #return a default value
73 return []
74
Steven M. Gavac11ccf32001-09-24 09:43:17 +000075 def Load(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040076 "Load the configuration file from disk."
terryjreedy349abd92017-07-07 16:00:57 -040077 if self.file:
78 self.read(self.file)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000079
Steven M. Gavac11ccf32001-09-24 09:43:17 +000080class IdleUserConfParser(IdleConfParser):
81 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000082 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000083 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000084
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040085 def SetOption(self, section, option, value):
86 """Return True if option is added or changed to value, else False.
87
88 Add section if required. False means option already had value.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000089 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040090 if self.has_option(section, option):
91 if self.get(section, option) == value:
92 return False
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000093 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040094 self.set(section, option, value)
95 return True
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000096 else:
97 if not self.has_section(section):
98 self.add_section(section)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040099 self.set(section, option, value)
100 return True
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000101
Louie Lu50c94352017-07-13 02:05:32 +0800102 def RemoveOption(self, section, option):
103 """Return True if option is removed from section, else False.
104
105 False if either section does not exist or did not have option.
106 """
107 if self.has_section(section):
108 return self.remove_option(section, option)
109 return False
110
111 def AddSection(self, section):
112 "If section doesn't exist, add it."
113 if not self.has_section(section):
114 self.add_section(section)
115
116 def RemoveEmptySections(self):
117 "Remove any sections that have no options."
118 for section in self.sections():
119 if not self.GetOptionList(section):
120 self.remove_section(section)
121
122 def IsEmpty(self):
123 "Return True if no sections after removing empty sections."
124 self.RemoveEmptySections()
125 return not self.sections()
126
Steven M. Gavab77d3432002-03-02 07:16:21 +0000127 def RemoveFile(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400128 "Remove user config file self.file from disk if it exists."
Steven M. Gavab77d3432002-03-02 07:16:21 +0000129 if os.path.exists(self.file):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000130 os.remove(self.file)
131
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000132 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000133 """Update user configuration file.
134
terryjreedy349abd92017-07-07 16:00:57 -0400135 If self not empty after removing empty sections, write the file
136 to disk. Otherwise, remove the file from disk if it exists.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000137
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000138 """
terryjreedy349abd92017-07-07 16:00:57 -0400139 fname = self.file
140 if fname:
141 if not self.IsEmpty():
142 try:
143 cfgFile = open(fname, 'w')
144 except OSError:
145 os.unlink(fname)
146 cfgFile = open(fname, 'w')
147 with cfgFile:
148 self.write(cfgFile)
149 else:
150 self.RemoveFile()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000151
152class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400153 """Hold config parsers for all idle config files in singleton instance.
154
155 Default config files, self.defaultCfg --
156 for config_type in self.config_types:
157 (idle install dir)/config-{config-type}.def
158
159 User config files, self.userCfg --
160 for config_type in self.config_types:
161 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000162 """
Louie Luf776eb02017-07-19 05:17:56 +0800163 def __init__(self, _utest=False):
terryjreedy349abd92017-07-07 16:00:57 -0400164 self.config_types = ('main', 'highlight', 'keys', 'extensions')
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400165 self.defaultCfg = {}
166 self.userCfg = {}
167 self.cfg = {} # TODO use to select userCfg vs defaultCfg
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400168
Louie Luf776eb02017-07-19 05:17:56 +0800169 if not _utest:
170 self.CreateConfigHandlers()
171 self.LoadCfgFiles()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000172
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000173 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400174 "Populate default and user config parser dictionaries."
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000175 #build idle install path
176 if __name__ != '__main__': # we were imported
terryjreedy223c7e72017-07-07 22:28:06 -0400177 idleDir = os.path.dirname(__file__)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000178 else: # we were exec'ed (for testing only)
terryjreedy223c7e72017-07-07 22:28:06 -0400179 idleDir = os.path.abspath(sys.path[0])
180 self.userdir = userDir = self.GetUserCfgDir()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400181
182 defCfgFiles = {}
183 usrCfgFiles = {}
184 # TODO eliminate these temporaries by combining loops
185 for cfgType in self.config_types: #build config file names
186 defCfgFiles[cfgType] = os.path.join(
187 idleDir, 'config-' + cfgType + '.def')
188 usrCfgFiles[cfgType] = os.path.join(
189 userDir, 'config-' + cfgType + '.cfg')
190 for cfgType in self.config_types: #create config parsers
191 self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
192 self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000193
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000194 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400195 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000196
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400197 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000198 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000199 cfgDir = '.idlerc'
200 userDir = os.path.expanduser('~')
201 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000202 if not os.path.exists(userDir):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400203 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
204 userDir + ',\n but the path does not exist.')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000205 try:
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400206 print(warn, file=sys.stderr)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200207 except OSError:
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000208 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000209 userDir = '~'
210 if userDir == "~": # still no path to home!
211 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
212 userDir = os.getcwd()
213 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000214 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000215 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000216 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200217 except OSError:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400218 warn = ('\n Warning: unable to create user config directory\n' +
219 userDir + '\n Check path and permissions.\n Exiting!\n')
Louie Luf776eb02017-07-19 05:17:56 +0800220 if not idlelib.testing:
221 print(warn, file=sys.stderr)
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000222 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400223 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000224 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000225
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000226 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000227 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400228 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000229
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400230 If type is not None, return a value of that type. Also pass raw
231 to the config parser. First try to return a valid value
232 (including type) from a user configuration. If that fails, try
233 the default configuration. If that fails, return default, with a
234 default of None.
235
236 Warn if either user or default configurations have an invalid value.
237 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000238 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200239 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400240 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200241 return self.userCfg[configType].Get(section, option,
242 type=type, raw=raw)
243 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400244 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200245 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400246 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200247 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400248 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400249 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200250 try:
251 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400252 return self.defaultCfg[configType].Get(
253 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200254 except ValueError:
255 pass
256 #returning default, print warning
257 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400258 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200259 ' problem retrieving configuration option %r\n'
260 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400261 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200262 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400263 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200264 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000265
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000266 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400267 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000268 self.userCfg[configType].SetOption(section, option, value)
269
Steven M. Gava2a63a072001-10-26 06:50:54 +0000270 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400271 """Return sections for configSet configType configuration.
272
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000273 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400274 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000275 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400276 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000277 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000278 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400279 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000280 elif configSet == 'default':
281 cfgParser=self.defaultCfg[configType]
282 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000283 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000284 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000285
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000286 def GetHighlight(self, theme, element, fgBg=None):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400287 """Return individual theme element highlight color(s).
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400288
Terry Jan Reedy86757992014-10-09 18:44:32 -0400289 fgBg - string ('fg' or 'bg') or None.
290 If None, return a dictionary containing fg and bg colors with
291 keys 'foreground' and 'background'. Otherwise, only return
292 fg or bg color, as specified. Colors are intended to be
293 appropriate for passing to Tkinter in, e.g., a tag_config call).
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000294 """
Steven M. Gava9f25e672002-02-11 02:51:18 +0000295 if self.defaultCfg['highlight'].has_section(theme):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400296 themeDict = self.GetThemeDict('default', theme)
Steven M. Gava9f25e672002-02-11 02:51:18 +0000297 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400298 themeDict = self.GetThemeDict('user', theme)
299 fore = themeDict[element + '-foreground']
Terry Jan Reedy86757992014-10-09 18:44:32 -0400300 if element == 'cursor': # There is no config value for cursor bg
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400301 back = themeDict['normal-background']
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000302 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400303 back = themeDict[element + '-background']
304 highlight = {"foreground": fore, "background": back}
Terry Jan Reedy86757992014-10-09 18:44:32 -0400305 if not fgBg: # Return dict of both colors
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000306 return highlight
Terry Jan Reedy86757992014-10-09 18:44:32 -0400307 else: # Return specified color only
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000308 if fgBg == 'fg':
309 return highlight["foreground"]
310 if fgBg == 'bg':
311 return highlight["background"]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000312 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000313 raise InvalidFgBg('Invalid fgBg specified')
Steven M. Gava9f25e672002-02-11 02:51:18 +0000314
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400315 def GetThemeDict(self, type, themeName):
316 """Return {option:value} dict for elements in themeName.
317
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000318 type - string, 'default' or 'user' theme type
319 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400320 Values are loaded over ultimate fallback defaults to guarantee
321 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000322 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000323 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400324 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000325 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400326 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000327 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000328 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400329 # Provide foreground and background colors for each theme
330 # element (other than cursor) even though some values are not
331 # yet used by idle, to allow for their use in the future.
332 # Default values are generally black and white.
333 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400334 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000335 'normal-background':'#ffffff',
336 'keyword-foreground':'#000000',
337 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000338 'builtin-foreground':'#000000',
339 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000340 'comment-foreground':'#000000',
341 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000342 'string-foreground':'#000000',
343 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000344 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000345 'definition-background':'#ffffff',
346 'hilite-foreground':'#000000',
347 'hilite-background':'gray',
348 'break-foreground':'#ffffff',
349 'break-background':'#000000',
350 'hit-foreground':'#ffffff',
351 'hit-background':'#000000',
352 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000353 'error-background':'#000000',
354 #cursor (only foreground can be set)
355 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000356 #shell window
357 'stdout-foreground':'#000000',
358 'stdout-background':'#ffffff',
359 'stderr-foreground':'#000000',
360 'stderr-background':'#ffffff',
361 'console-foreground':'#000000',
362 'console-background':'#ffffff' }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000363 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400364 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400365 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400366 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000367 ' -\n problem retrieving theme element %r'
368 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400369 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000370 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400371 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400372 theme[element] = cfgParser.Get(
373 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000374 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000375
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000376 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400377 "Return the name of the currently active text color theme."
378 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500379
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400380 def CurrentKeys(self):
381 """Return the name of the currently active key set."""
382 return self.current_colors_and_keys('Keys')
383
384 def current_colors_and_keys(self, section):
385 """Return the currently active name for Theme or Keys section.
386
387 idlelib.config-main.def ('default') includes these sections
388
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500389 [Theme]
390 default= 1
391 name= IDLE Classic
392 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500393
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400394 [Keys]
395 default= 1
396 name=
397 name2=
398
399 Item 'name2', is used for built-in ('default') themes and keys
400 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
401 because setting 'name' to a builtin not defined in older IDLEs
402 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500403 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400404 When default = True, 'name2' takes precedence over 'name',
405 while older IDLEs will just use name. When default = False,
406 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500407 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400409 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500410 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400411 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500412 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400413 name = self.GetOption('main', section, 'name2', default='')
414 if not name:
415 name = self.GetOption('main', section, 'name', default='')
416 if name:
417 source = self.defaultCfg if default else self.userCfg
418 if source[cfgname].has_section(name):
419 return name
420 return "IDLE Classic" if section == 'Theme' else self.default_keys()
421
422 @staticmethod
423 def default_keys():
424 if sys.platform[:3] == 'win':
425 return 'IDLE Classic Windows'
426 elif sys.platform == 'darwin':
427 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500428 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400429 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000430
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400431 def GetExtensions(self, active_only=True,
432 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400433 """Return extensions in default and user config-extensions files.
434
435 If active_only True, only return active (enabled) extensions
436 and optionally only editor or shell extensions.
437 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000438 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400439 extns = self.RemoveKeyBindNames(
440 self.GetSectionList('default', 'extensions'))
441 userExtns = self.RemoveKeyBindNames(
442 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000443 for extn in userExtns:
444 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000445 extns.append(extn)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000446 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400447 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000448 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000449 if self.GetOption('extensions', extn, 'enable', default=True,
450 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000451 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400452 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000453 if editor_only:
454 option = "enable_editor"
455 else:
456 option = "enable_shell"
457 if self.GetOption('extensions', extn,option,
458 default=True, type='bool',
459 warn_on_default=False):
460 activeExtns.append(extn)
461 else:
462 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000463 return activeExtns
464 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000465 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000466
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400467 def RemoveKeyBindNames(self, extnNameList):
468 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800469 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000470
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400471 def GetExtnNameForEvent(self, virtualEvent):
472 """Return the name of the extension binding virtualEvent, or None.
473
474 virtualEvent - string, name of the virtual event to test for,
475 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000476 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400477 extName = None
478 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000479 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000480 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000481 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400482 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000483 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000484
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400485 def GetExtensionKeys(self, extensionName):
486 """Return dict: {configurable extensionName event : active keybinding}.
487
488 Events come from default config extension_cfgBindings section.
489 Keybindings come from GetCurrentKeySet() active key dict,
490 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000491 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400492 keysName = extensionName + '_cfgBindings'
493 activeKeys = self.GetCurrentKeySet()
494 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000495 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400496 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000497 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400498 event = '<<' + eventName + '>>'
499 binding = activeKeys[event]
500 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000501 return extKeys
502
Steven M. Gavac628a062002-01-19 10:33:21 +0000503 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400504 """Return dict {configurable extensionName event : keybinding list}.
505
506 Events come from default config extension_cfgBindings section.
507 Keybindings list come from the splitting of GetOption, which
508 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000509 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400510 keysName = extensionName+'_cfgBindings'
511 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000512 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400513 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000514 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400515 binding = self.GetOption(
516 'extensions', keysName, eventName, default='').split()
517 event = '<<' + eventName + '>>'
518 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000519 return extKeys
520
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400521 def GetExtensionBindings(self, extensionName):
522 """Return dict {extensionName event : active or defined keybinding}.
523
524 Augment self.GetExtensionKeys(extensionName) with mapping of non-
525 configurable events (from default config) to GetOption splits,
526 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000527 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400528 bindsName = extensionName + '_bindings'
529 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000530 #add the non-configurable bindings
531 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400532 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000533 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400534 binding = self.GetOption(
535 'extensions', bindsName, eventName, default='').split()
536 event = '<<' + eventName + '>>'
537 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
539 return extBinds
540
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000541 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400542 """Return the keybinding list for keySetName eventStr.
543
544 keySetName - name of key binding set (config-keys section).
545 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000546 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400547 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400548 binding = self.GetOption('keys', keySetName, eventName, default='',
549 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000550 return binding
551
Steven M. Gavac628a062002-01-19 10:33:21 +0000552 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400553 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000554 result = self.GetKeySet(self.CurrentKeys())
555
Ned Deilyb7601672014-03-27 20:49:14 -0700556 if sys.platform == "darwin":
557 # OS X Tk variants do not support the "Alt" keyboard modifier.
558 # So replace all keybingings that use "Alt" with ones that
559 # use the "Option" keyboard modifier.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400560 # TODO (Ned?): the "Option" modifier does not work properly for
Ned Deilyb7601672014-03-27 20:49:14 -0700561 # Cocoa Tk and XQuartz Tk so we should not use it
562 # in default OS X KeySets.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563 for k, v in result.items():
564 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
565 if v != v2:
566 result[k] = v2
567
568 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000569
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400570 def GetKeySet(self, keySetName):
571 """Return event-key dict for keySetName core plus active extensions.
572
573 If a binding defined in an extension is already in use, the
574 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000575 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400576 keySet = self.GetCoreKeys(keySetName)
577 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000578 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400579 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000580 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000581 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000582 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000583 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400584 extKeys[event] = '' #disable this binding
585 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000586 return keySet
587
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400588 def IsCoreBinding(self, virtualEvent):
589 """Return True if the virtual event is one of the core idle key events.
590
591 virtualEvent - string, name of the virtual event to test for,
592 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000593 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000594 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000595
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400596# TODO make keyBindins a file or class attribute used for test above
597# and copied in function below
598
Steven M. Gavac628a062002-01-19 10:33:21 +0000599 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400600 """Return dict of core virtual-key keybindings for keySetName.
601
602 The default keySetName None corresponds to the keyBindings base
603 dict. If keySetName is not None, bindings from the config
604 file(s) are loaded _over_ these defaults, so if there is a
605 problem getting any core binding there will be an 'ultimate last
606 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000607 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000608 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000609 '<<copy>>': ['<Control-c>', '<Control-C>'],
610 '<<cut>>': ['<Control-x>', '<Control-X>'],
611 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000612 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
613 '<<center-insert>>': ['<Control-l>'],
614 '<<close-all-windows>>': ['<Control-q>'],
615 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000616 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000617 '<<end-of-file>>': ['<Control-d>'],
618 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000619 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000620 '<<history-next>>': ['<Alt-n>'],
621 '<<history-previous>>': ['<Alt-p>'],
622 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000623 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000624 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000625 '<<open-class-browser>>': ['<Alt-c>'],
626 '<<open-module>>': ['<Alt-m>'],
627 '<<open-new-window>>': ['<Control-n>'],
628 '<<open-window-from-file>>': ['<Control-o>'],
629 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000630 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000631 '<<redo>>': ['<Control-y>'],
632 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000633 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000634 '<<save-window-as-file>>': ['<Alt-s>'],
635 '<<save-window>>': ['<Control-s>'],
636 '<<select-all>>': ['<Alt-a>'],
637 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000638 '<<undo>>': ['<Control-z>'],
639 '<<find-again>>': ['<Control-g>', '<F3>'],
640 '<<find-in-files>>': ['<Alt-F3>'],
641 '<<find-selection>>': ['<Control-F3>'],
642 '<<find>>': ['<Control-f>'],
643 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000644 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000645 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300646 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000647 '<<smart-indent>>': ['<Key-Tab>'],
648 '<<indent-region>>': ['<Control-Key-bracketright>'],
649 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
650 '<<comment-region>>': ['<Alt-Key-3>'],
651 '<<uncomment-region>>': ['<Alt-Key-4>'],
652 '<<tabify-region>>': ['<Alt-Key-5>'],
653 '<<untabify-region>>': ['<Alt-Key-6>'],
654 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000655 '<<change-indentwidth>>': ['<Alt-Key-u>'],
656 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
657 '<<del-word-right>>': ['<Control-Key-Delete>']
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000658 }
Steven M. Gava17d01542001-12-03 00:37:28 +0000659 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400660 if not (self.userCfg['keys'].has_section(keySetName) or
661 self.defaultCfg['keys'].has_section(keySetName)):
662 warning = (
663 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
664 ' key set %r is not defined, using default bindings.' %
665 (keySetName,)
666 )
667 _warn(warning, 'keys', keySetName)
668 else:
669 for event in keyBindings:
670 binding = self.GetKeyBinding(keySetName, event)
671 if binding:
672 keyBindings[event] = binding
673 else: #we are going to return a default, print warning
674 warning = (
675 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
676 ' problem retrieving key binding for event %r\n'
677 ' from key set %r.\n'
678 ' returning default value: %r' %
679 (event, keySetName, keyBindings[event])
680 )
681 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000682 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000683
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400684 def GetExtraHelpSourceList(self, configSet):
685 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000686
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000687 Valid configSets are 'user' or 'default'. Return a list of tuples of
688 the form (menu_item , path_to_help_file , option), or return the empty
689 list. 'option' is the sequence number of the help resource. 'option'
690 values determine the position of the menu items on the Help menu,
691 therefore the returned list must be sorted by 'option'.
692
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000693 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400694 helpSources = []
695 if configSet == 'user':
696 cfgParser = self.userCfg['main']
697 elif configSet == 'default':
698 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000699 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000700 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000701 options=cfgParser.GetOptionList('HelpFiles')
702 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400703 value=cfgParser.Get('HelpFiles', option, default=';')
704 if value.find(';') == -1: #malformed config entry with no ';'
705 menuItem = '' #make these empty
706 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000707 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000708 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000709 menuItem=value[0].strip()
710 helpPath=value[1].strip()
711 if menuItem and helpPath: #neither are empty strings
712 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000713 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000714 return helpSources
715
716 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400717 """Return a list of the details of all additional help sources.
718
719 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000720 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400721 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000722 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000723 return allHelpSources
724
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400725 def GetFont(self, root, configType, section):
726 """Retrieve a font from configuration (font, font-size, font-bold)
727 Intercept the special value 'TkFixedFont' and substitute
728 the actual font, factoring in some tweaks if needed for
729 appearance sakes.
730
731 The 'root' parameter can normally be any valid Tkinter widget.
732
733 Return a tuple (family, size, weight) suitable for passing
734 to tkinter.Font
735 """
736 family = self.GetOption(configType, section, 'font', default='courier')
737 size = self.GetOption(configType, section, 'font-size', type='int',
738 default='10')
739 bold = self.GetOption(configType, section, 'font-bold', default=0,
740 type='bool')
741 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400742 f = Font(name='TkFixedFont', exists=True, root=root)
743 actualFont = Font.actual(f)
744 family = actualFont['family']
745 size = actualFont['size']
746 if size <= 0:
747 size = 10 # if font in pixels, ignore actual size
748 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400749 return (family, size, 'bold' if bold else 'normal')
750
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000751 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400752 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000753 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000754 self.defaultCfg[key].Load()
755 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000756
757 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400758 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000759 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000760 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000761
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000762
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400763idleConf = IdleConf()
764
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400765_warned = set()
766def _warn(msg, *key):
767 key = (msg,) + key
768 if key not in _warned:
769 try:
770 print(msg, file=sys.stderr)
771 except OSError:
772 pass
773 _warned.add(key)
774
775
terryjreedy349abd92017-07-07 16:00:57 -0400776class ConfigChanges(dict):
777 """Manage a user's proposed configuration option changes.
778
779 Names used across multiple methods:
780 page -- one of the 4 top-level dicts representing a
781 .idlerc/config-x.cfg file.
782 config_type -- name of a page.
783 section -- a section within a page/file.
784 option -- name of an option within a section.
785 value -- value for the option.
786
787 Methods
788 add_option: Add option and value to changes.
789 save_option: Save option and value to config parser.
790 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400791 delete_section: If section exists,
792 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400793 clear: Clear all changes by clearing each page.
794 """
795 def __init__(self):
796 "Create a page for each configuration file"
797 self.pages = [] # List of unhashable dicts.
798 for config_type in idleConf.config_types:
799 self[config_type] = {}
800 self.pages.append(self[config_type])
801
802 def add_option(self, config_type, section, item, value):
803 "Add item/value pair for config_type and section."
804 page = self[config_type]
805 value = str(value) # Make sure we use a string.
806 if section not in page:
807 page[section] = {}
808 page[section][item] = value
809
810 @staticmethod
811 def save_option(config_type, section, item, value):
812 """Return True if the configuration value was added or changed.
813
814 Helper for save_all.
815 """
816 if idleConf.defaultCfg[config_type].has_option(section, item):
817 if idleConf.defaultCfg[config_type].Get(section, item) == value:
818 # The setting equals a default setting, remove it from user cfg.
819 return idleConf.userCfg[config_type].RemoveOption(section, item)
820 # If we got here, set the option.
821 return idleConf.userCfg[config_type].SetOption(section, item, value)
822
823 def save_all(self):
824 """Save configuration changes to the user config file.
825
Louie Lu50c94352017-07-13 02:05:32 +0800826 Clear self in preparation for additional changes.
827 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400828 """
829 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800830
831 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400832 for config_type in self:
833 cfg_type_changed = False
834 page = self[config_type]
835 for section in page:
836 if section == 'HelpFiles': # Remove it for replacement.
837 idleConf.userCfg['main'].remove_section('HelpFiles')
838 cfg_type_changed = True
839 for item, value in page[section].items():
840 if self.save_option(config_type, section, item, value):
841 cfg_type_changed = True
842 if cfg_type_changed:
843 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800844 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400845 for config_type in ['keys', 'highlight']:
846 # Save these even if unchanged!
847 idleConf.userCfg[config_type].Save()
848 self.clear()
849 # ConfigDialog caller must add the following call
850 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800851 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400852
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
terryjreedy349abd92017-07-07 16:00:57 -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)
terryjreedy349abd92017-07-07 16:00:57 -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):
terryjreedy349abd92017-07-07 16:00:57 -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='')
terryjreedy349abd92017-07-07 16:00:57 -0400903
904if __name__ == '__main__':
905 import unittest
906 unittest.main('idlelib.idle_test.test_config',
907 verbosity=2, exit=False)
908 #_dump()