blob: c3e57bc692d801fd251928281283ca826b2db452 [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',
wohlganger58fc71c2017-09-10 16:19:47 -0500362 'console-background':'#ffffff',
363 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000364 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400365 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400366 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400367 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000368 ' -\n problem retrieving theme element %r'
369 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400370 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000371 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400372 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400373 theme[element] = cfgParser.Get(
374 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000375 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000376
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000377 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400378 "Return the name of the currently active text color theme."
379 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500380
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400381 def CurrentKeys(self):
382 """Return the name of the currently active key set."""
383 return self.current_colors_and_keys('Keys')
384
385 def current_colors_and_keys(self, section):
386 """Return the currently active name for Theme or Keys section.
387
388 idlelib.config-main.def ('default') includes these sections
389
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500390 [Theme]
391 default= 1
392 name= IDLE Classic
393 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500394
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400395 [Keys]
396 default= 1
397 name=
398 name2=
399
400 Item 'name2', is used for built-in ('default') themes and keys
401 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
402 because setting 'name' to a builtin not defined in older IDLEs
403 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500404 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400405 When default = True, 'name2' takes precedence over 'name',
406 while older IDLEs will just use name. When default = False,
407 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500408 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400409 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400410 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500411 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400412 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500413 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400414 name = self.GetOption('main', section, 'name2', default='')
415 if not name:
416 name = self.GetOption('main', section, 'name', default='')
417 if name:
418 source = self.defaultCfg if default else self.userCfg
419 if source[cfgname].has_section(name):
420 return name
421 return "IDLE Classic" if section == 'Theme' else self.default_keys()
422
423 @staticmethod
424 def default_keys():
425 if sys.platform[:3] == 'win':
426 return 'IDLE Classic Windows'
427 elif sys.platform == 'darwin':
428 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500429 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400430 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000431
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400432 def GetExtensions(self, active_only=True,
433 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400434 """Return extensions in default and user config-extensions files.
435
436 If active_only True, only return active (enabled) extensions
437 and optionally only editor or shell extensions.
438 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000439 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400440 extns = self.RemoveKeyBindNames(
441 self.GetSectionList('default', 'extensions'))
442 userExtns = self.RemoveKeyBindNames(
443 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000444 for extn in userExtns:
445 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000446 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500447 for extn in ('AutoComplete','CodeContext',
448 'FormatParagraph','ParenMatch'):
449 extns.remove(extn)
450 # specific exclusions because we are storing config for mainlined old
451 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000452 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400453 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000454 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000455 if self.GetOption('extensions', extn, 'enable', default=True,
456 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000457 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400458 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000459 if editor_only:
460 option = "enable_editor"
461 else:
462 option = "enable_shell"
463 if self.GetOption('extensions', extn,option,
464 default=True, type='bool',
465 warn_on_default=False):
466 activeExtns.append(extn)
467 else:
468 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000469 return activeExtns
470 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000471 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000472
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400473 def RemoveKeyBindNames(self, extnNameList):
474 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800475 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
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
wohlganger58fc71c2017-09-10 16:19:47 -0500603# and copied in function below.
604
605 former_extension_events = { # Those with user-configurable keys.
606 '<<force-open-completions>>', '<<expand-word>>',
607 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
608 '<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400609
Steven M. Gavac628a062002-01-19 10:33:21 +0000610 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400611 """Return dict of core virtual-key keybindings for keySetName.
612
613 The default keySetName None corresponds to the keyBindings base
614 dict. If keySetName is not None, bindings from the config
615 file(s) are loaded _over_ these defaults, so if there is a
616 problem getting any core binding there will be an 'ultimate last
617 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000618 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000619 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000620 '<<copy>>': ['<Control-c>', '<Control-C>'],
621 '<<cut>>': ['<Control-x>', '<Control-X>'],
622 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000623 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
624 '<<center-insert>>': ['<Control-l>'],
625 '<<close-all-windows>>': ['<Control-q>'],
626 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000627 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000628 '<<end-of-file>>': ['<Control-d>'],
629 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000630 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000631 '<<history-next>>': ['<Alt-n>'],
632 '<<history-previous>>': ['<Alt-p>'],
633 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000634 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000635 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000636 '<<open-class-browser>>': ['<Alt-c>'],
637 '<<open-module>>': ['<Alt-m>'],
638 '<<open-new-window>>': ['<Control-n>'],
639 '<<open-window-from-file>>': ['<Control-o>'],
640 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000641 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000642 '<<redo>>': ['<Control-y>'],
643 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000644 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000645 '<<save-window-as-file>>': ['<Alt-s>'],
646 '<<save-window>>': ['<Control-s>'],
647 '<<select-all>>': ['<Alt-a>'],
648 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000649 '<<undo>>': ['<Control-z>'],
650 '<<find-again>>': ['<Control-g>', '<F3>'],
651 '<<find-in-files>>': ['<Alt-F3>'],
652 '<<find-selection>>': ['<Control-F3>'],
653 '<<find>>': ['<Control-f>'],
654 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000655 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000656 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300657 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000658 '<<smart-indent>>': ['<Key-Tab>'],
659 '<<indent-region>>': ['<Control-Key-bracketright>'],
660 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
661 '<<comment-region>>': ['<Alt-Key-3>'],
662 '<<uncomment-region>>': ['<Alt-Key-4>'],
663 '<<tabify-region>>': ['<Alt-Key-5>'],
664 '<<untabify-region>>': ['<Alt-Key-6>'],
665 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000666 '<<change-indentwidth>>': ['<Alt-Key-u>'],
667 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500668 '<<del-word-right>>': ['<Control-Key-Delete>'],
669 '<<force-open-completions>>': ['<Control-Key-space>'],
670 '<<expand-word>>': ['<Alt-Key-slash>'],
671 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
672 '<<flash-paren>>': ['<Control-Key-0>'],
673 '<<format-paragraph>>': ['<Alt-Key-q>'],
674 '<<run-module>>': ['<Key-F5>'],
675 '<<check-module>>': ['<Alt-Key-x>'],
676 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000677 }
wohlganger58fc71c2017-09-10 16:19:47 -0500678
Steven M. Gava17d01542001-12-03 00:37:28 +0000679 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400680 if not (self.userCfg['keys'].has_section(keySetName) or
681 self.defaultCfg['keys'].has_section(keySetName)):
682 warning = (
683 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
684 ' key set %r is not defined, using default bindings.' %
685 (keySetName,)
686 )
687 _warn(warning, 'keys', keySetName)
688 else:
689 for event in keyBindings:
690 binding = self.GetKeyBinding(keySetName, event)
691 if binding:
692 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500693 # Otherwise return default in keyBindings.
694 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400695 warning = (
696 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
697 ' problem retrieving key binding for event %r\n'
698 ' from key set %r.\n'
699 ' returning default value: %r' %
700 (event, keySetName, keyBindings[event])
701 )
702 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000703 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000704
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400705 def GetExtraHelpSourceList(self, configSet):
706 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000707
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000708 Valid configSets are 'user' or 'default'. Return a list of tuples of
709 the form (menu_item , path_to_help_file , option), or return the empty
710 list. 'option' is the sequence number of the help resource. 'option'
711 values determine the position of the menu items on the Help menu,
712 therefore the returned list must be sorted by 'option'.
713
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000714 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400715 helpSources = []
716 if configSet == 'user':
717 cfgParser = self.userCfg['main']
718 elif configSet == 'default':
719 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000720 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000721 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000722 options=cfgParser.GetOptionList('HelpFiles')
723 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400724 value=cfgParser.Get('HelpFiles', option, default=';')
725 if value.find(';') == -1: #malformed config entry with no ';'
726 menuItem = '' #make these empty
727 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000728 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000729 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000730 menuItem=value[0].strip()
731 helpPath=value[1].strip()
732 if menuItem and helpPath: #neither are empty strings
733 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000734 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000735 return helpSources
736
737 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400738 """Return a list of the details of all additional help sources.
739
740 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000741 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400742 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000743 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000744 return allHelpSources
745
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400746 def GetFont(self, root, configType, section):
747 """Retrieve a font from configuration (font, font-size, font-bold)
748 Intercept the special value 'TkFixedFont' and substitute
749 the actual font, factoring in some tweaks if needed for
750 appearance sakes.
751
752 The 'root' parameter can normally be any valid Tkinter widget.
753
754 Return a tuple (family, size, weight) suitable for passing
755 to tkinter.Font
756 """
757 family = self.GetOption(configType, section, 'font', default='courier')
758 size = self.GetOption(configType, section, 'font-size', type='int',
759 default='10')
760 bold = self.GetOption(configType, section, 'font-bold', default=0,
761 type='bool')
762 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400763 f = Font(name='TkFixedFont', exists=True, root=root)
764 actualFont = Font.actual(f)
765 family = actualFont['family']
766 size = actualFont['size']
767 if size <= 0:
768 size = 10 # if font in pixels, ignore actual size
769 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400770 return (family, size, 'bold' if bold else 'normal')
771
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000772 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400773 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000774 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000775 self.defaultCfg[key].Load()
776 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000777
778 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400779 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000780 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000781 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000782
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000783
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400784idleConf = IdleConf()
785
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400786_warned = set()
787def _warn(msg, *key):
788 key = (msg,) + key
789 if key not in _warned:
790 try:
791 print(msg, file=sys.stderr)
792 except OSError:
793 pass
794 _warned.add(key)
795
796
terryjreedy349abd92017-07-07 16:00:57 -0400797class ConfigChanges(dict):
798 """Manage a user's proposed configuration option changes.
799
800 Names used across multiple methods:
801 page -- one of the 4 top-level dicts representing a
802 .idlerc/config-x.cfg file.
803 config_type -- name of a page.
804 section -- a section within a page/file.
805 option -- name of an option within a section.
806 value -- value for the option.
807
808 Methods
809 add_option: Add option and value to changes.
810 save_option: Save option and value to config parser.
811 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400812 delete_section: If section exists,
813 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400814 clear: Clear all changes by clearing each page.
815 """
816 def __init__(self):
817 "Create a page for each configuration file"
818 self.pages = [] # List of unhashable dicts.
819 for config_type in idleConf.config_types:
820 self[config_type] = {}
821 self.pages.append(self[config_type])
822
823 def add_option(self, config_type, section, item, value):
824 "Add item/value pair for config_type and section."
825 page = self[config_type]
826 value = str(value) # Make sure we use a string.
827 if section not in page:
828 page[section] = {}
829 page[section][item] = value
830
831 @staticmethod
832 def save_option(config_type, section, item, value):
833 """Return True if the configuration value was added or changed.
834
835 Helper for save_all.
836 """
837 if idleConf.defaultCfg[config_type].has_option(section, item):
838 if idleConf.defaultCfg[config_type].Get(section, item) == value:
839 # The setting equals a default setting, remove it from user cfg.
840 return idleConf.userCfg[config_type].RemoveOption(section, item)
841 # If we got here, set the option.
842 return idleConf.userCfg[config_type].SetOption(section, item, value)
843
844 def save_all(self):
845 """Save configuration changes to the user config file.
846
Louie Lu50c94352017-07-13 02:05:32 +0800847 Clear self in preparation for additional changes.
848 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400849 """
850 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800851
852 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400853 for config_type in self:
854 cfg_type_changed = False
855 page = self[config_type]
856 for section in page:
857 if section == 'HelpFiles': # Remove it for replacement.
858 idleConf.userCfg['main'].remove_section('HelpFiles')
859 cfg_type_changed = True
860 for item, value in page[section].items():
861 if self.save_option(config_type, section, item, value):
862 cfg_type_changed = True
863 if cfg_type_changed:
864 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800865 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400866 for config_type in ['keys', 'highlight']:
867 # Save these even if unchanged!
868 idleConf.userCfg[config_type].Save()
869 self.clear()
870 # ConfigDialog caller must add the following call
871 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800872 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400873
874 def delete_section(self, config_type, section):
875 """Delete a section from self, userCfg, and file.
876
877 Used to delete custom themes and keysets.
878 """
879 if section in self[config_type]:
880 del self[config_type][section]
881 configpage = idleConf.userCfg[config_type]
882 configpage.remove_section(section)
883 configpage.Save()
884
885 def clear(self):
886 """Clear all 4 pages.
887
888 Called in save_all after saving to idleConf.
889 XXX Mark window *title* when there are changes; unmark here.
890 """
891 for page in self.pages:
892 page.clear()
893
894
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400895# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400896def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400897 from zlib import crc32
898 line, crc = 0, 0
899
900 def sprint(obj):
901 global line, crc
902 txt = str(obj)
903 line += 1
904 crc = crc32(txt.encode(encoding='utf-8'), crc)
905 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400906 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400907
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000908 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400909 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400910 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400911 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400912 sprint(key)
913 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000914 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400915 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400916 sprint(section)
917 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000918 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400919 sprint(option + ' = ' + cfg[key].Get(section, option))
920
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000921 dumpCfg(idleConf.defaultCfg)
922 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400923 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400924
925if __name__ == '__main__':
926 import unittest
927 unittest.main('idlelib.idle_test.test_config',
928 verbosity=2, exit=False)
929 #_dump()