blob: 0eb90fc8dc5fd6718c0b008ec0fc187bbfe1fac1 [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',
Cheryl Sabellade651622018-06-01 21:45:54 -0400363 'context-foreground':'#000000',
364 'context-background':'#ffffff',
wohlganger58fc71c2017-09-10 16:19:47 -0500365 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000366 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400367 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400368 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400369 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000370 ' -\n problem retrieving theme element %r'
371 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400372 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000373 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400374 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400375 theme[element] = cfgParser.Get(
376 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000377 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000378
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000379 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400380 "Return the name of the currently active text color theme."
381 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500382
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400383 def CurrentKeys(self):
384 """Return the name of the currently active key set."""
385 return self.current_colors_and_keys('Keys')
386
387 def current_colors_and_keys(self, section):
388 """Return the currently active name for Theme or Keys section.
389
390 idlelib.config-main.def ('default') includes these sections
391
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500392 [Theme]
393 default= 1
394 name= IDLE Classic
395 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500396
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400397 [Keys]
398 default= 1
399 name=
400 name2=
401
402 Item 'name2', is used for built-in ('default') themes and keys
403 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
404 because setting 'name' to a builtin not defined in older IDLEs
405 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500406 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400407 When default = True, 'name2' takes precedence over 'name',
408 while older IDLEs will just use name. When default = False,
409 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500410 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400411 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400412 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500413 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400414 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500415 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400416 name = self.GetOption('main', section, 'name2', default='')
417 if not name:
418 name = self.GetOption('main', section, 'name', default='')
419 if name:
420 source = self.defaultCfg if default else self.userCfg
421 if source[cfgname].has_section(name):
422 return name
423 return "IDLE Classic" if section == 'Theme' else self.default_keys()
424
425 @staticmethod
426 def default_keys():
427 if sys.platform[:3] == 'win':
428 return 'IDLE Classic Windows'
429 elif sys.platform == 'darwin':
430 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500431 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400432 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000433
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400434 def GetExtensions(self, active_only=True,
435 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400436 """Return extensions in default and user config-extensions files.
437
438 If active_only True, only return active (enabled) extensions
439 and optionally only editor or shell extensions.
440 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000441 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400442 extns = self.RemoveKeyBindNames(
443 self.GetSectionList('default', 'extensions'))
444 userExtns = self.RemoveKeyBindNames(
445 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000446 for extn in userExtns:
447 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000448 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500449 for extn in ('AutoComplete','CodeContext',
450 'FormatParagraph','ParenMatch'):
451 extns.remove(extn)
452 # specific exclusions because we are storing config for mainlined old
453 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000454 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400455 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000456 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000457 if self.GetOption('extensions', extn, 'enable', default=True,
458 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000459 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400460 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000461 if editor_only:
462 option = "enable_editor"
463 else:
464 option = "enable_shell"
465 if self.GetOption('extensions', extn,option,
466 default=True, type='bool',
467 warn_on_default=False):
468 activeExtns.append(extn)
469 else:
470 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000471 return activeExtns
472 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000473 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000474
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400475 def RemoveKeyBindNames(self, extnNameList):
476 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800477 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000478
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400479 def GetExtnNameForEvent(self, virtualEvent):
480 """Return the name of the extension binding virtualEvent, or None.
481
482 virtualEvent - string, name of the virtual event to test for,
483 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000484 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400485 extName = None
486 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000487 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000488 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000489 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400490 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000491 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000492
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400493 def GetExtensionKeys(self, extensionName):
494 """Return dict: {configurable extensionName event : active keybinding}.
495
496 Events come from default config extension_cfgBindings section.
497 Keybindings come from GetCurrentKeySet() active key dict,
498 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000499 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400500 keysName = extensionName + '_cfgBindings'
501 activeKeys = self.GetCurrentKeySet()
502 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000503 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400504 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000505 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400506 event = '<<' + eventName + '>>'
507 binding = activeKeys[event]
508 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000509 return extKeys
510
Steven M. Gavac628a062002-01-19 10:33:21 +0000511 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400512 """Return dict {configurable extensionName event : keybinding list}.
513
514 Events come from default config extension_cfgBindings section.
515 Keybindings list come from the splitting of GetOption, which
516 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000517 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400518 keysName = extensionName+'_cfgBindings'
519 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000520 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400521 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000522 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400523 binding = self.GetOption(
524 'extensions', keysName, eventName, default='').split()
525 event = '<<' + eventName + '>>'
526 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000527 return extKeys
528
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400529 def GetExtensionBindings(self, extensionName):
530 """Return dict {extensionName event : active or defined keybinding}.
531
532 Augment self.GetExtensionKeys(extensionName) with mapping of non-
533 configurable events (from default config) to GetOption splits,
534 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000535 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400536 bindsName = extensionName + '_bindings'
537 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000538 #add the non-configurable bindings
539 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400540 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000541 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400542 binding = self.GetOption(
543 'extensions', bindsName, eventName, default='').split()
544 event = '<<' + eventName + '>>'
545 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
547 return extBinds
548
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000549 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400550 """Return the keybinding list for keySetName eventStr.
551
552 keySetName - name of key binding set (config-keys section).
553 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000554 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400555 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400556 binding = self.GetOption('keys', keySetName, eventName, default='',
557 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000558 return binding
559
Steven M. Gavac628a062002-01-19 10:33:21 +0000560 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400561 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000562 result = self.GetKeySet(self.CurrentKeys())
563
Ned Deilyb7601672014-03-27 20:49:14 -0700564 if sys.platform == "darwin":
565 # OS X Tk variants do not support the "Alt" keyboard modifier.
566 # So replace all keybingings that use "Alt" with ones that
567 # use the "Option" keyboard modifier.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400568 # TODO (Ned?): the "Option" modifier does not work properly for
Ned Deilyb7601672014-03-27 20:49:14 -0700569 # Cocoa Tk and XQuartz Tk so we should not use it
570 # in default OS X KeySets.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000571 for k, v in result.items():
572 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
573 if v != v2:
574 result[k] = v2
575
576 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400578 def GetKeySet(self, keySetName):
579 """Return event-key dict for keySetName core plus active extensions.
580
581 If a binding defined in an extension is already in use, the
582 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000583 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400584 keySet = self.GetCoreKeys(keySetName)
585 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000586 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400587 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000588 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000589 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000590 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000591 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400592 extKeys[event] = '' #disable this binding
593 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000594 return keySet
595
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400596 def IsCoreBinding(self, virtualEvent):
597 """Return True if the virtual event is one of the core idle key events.
598
599 virtualEvent - string, name of the virtual event to test for,
600 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000601 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000602 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000603
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400604# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500605# and copied in function below.
606
607 former_extension_events = { # Those with user-configurable keys.
608 '<<force-open-completions>>', '<<expand-word>>',
609 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
610 '<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400611
Steven M. Gavac628a062002-01-19 10:33:21 +0000612 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400613 """Return dict of core virtual-key keybindings for keySetName.
614
615 The default keySetName None corresponds to the keyBindings base
616 dict. If keySetName is not None, bindings from the config
617 file(s) are loaded _over_ these defaults, so if there is a
618 problem getting any core binding there will be an 'ultimate last
619 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000620 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000621 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000622 '<<copy>>': ['<Control-c>', '<Control-C>'],
623 '<<cut>>': ['<Control-x>', '<Control-X>'],
624 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000625 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
626 '<<center-insert>>': ['<Control-l>'],
627 '<<close-all-windows>>': ['<Control-q>'],
628 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000629 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000630 '<<end-of-file>>': ['<Control-d>'],
631 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000632 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000633 '<<history-next>>': ['<Alt-n>'],
634 '<<history-previous>>': ['<Alt-p>'],
635 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000636 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000637 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000638 '<<open-class-browser>>': ['<Alt-c>'],
639 '<<open-module>>': ['<Alt-m>'],
640 '<<open-new-window>>': ['<Control-n>'],
641 '<<open-window-from-file>>': ['<Control-o>'],
642 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000643 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000644 '<<redo>>': ['<Control-y>'],
645 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000646 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000647 '<<save-window-as-file>>': ['<Alt-s>'],
648 '<<save-window>>': ['<Control-s>'],
649 '<<select-all>>': ['<Alt-a>'],
650 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000651 '<<undo>>': ['<Control-z>'],
652 '<<find-again>>': ['<Control-g>', '<F3>'],
653 '<<find-in-files>>': ['<Alt-F3>'],
654 '<<find-selection>>': ['<Control-F3>'],
655 '<<find>>': ['<Control-f>'],
656 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000657 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000658 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300659 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000660 '<<smart-indent>>': ['<Key-Tab>'],
661 '<<indent-region>>': ['<Control-Key-bracketright>'],
662 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
663 '<<comment-region>>': ['<Alt-Key-3>'],
664 '<<uncomment-region>>': ['<Alt-Key-4>'],
665 '<<tabify-region>>': ['<Alt-Key-5>'],
666 '<<untabify-region>>': ['<Alt-Key-6>'],
667 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000668 '<<change-indentwidth>>': ['<Alt-Key-u>'],
669 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500670 '<<del-word-right>>': ['<Control-Key-Delete>'],
671 '<<force-open-completions>>': ['<Control-Key-space>'],
672 '<<expand-word>>': ['<Alt-Key-slash>'],
673 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
674 '<<flash-paren>>': ['<Control-Key-0>'],
675 '<<format-paragraph>>': ['<Alt-Key-q>'],
676 '<<run-module>>': ['<Key-F5>'],
677 '<<check-module>>': ['<Alt-Key-x>'],
678 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000679 }
wohlganger58fc71c2017-09-10 16:19:47 -0500680
Steven M. Gava17d01542001-12-03 00:37:28 +0000681 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400682 if not (self.userCfg['keys'].has_section(keySetName) or
683 self.defaultCfg['keys'].has_section(keySetName)):
684 warning = (
685 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
686 ' key set %r is not defined, using default bindings.' %
687 (keySetName,)
688 )
689 _warn(warning, 'keys', keySetName)
690 else:
691 for event in keyBindings:
692 binding = self.GetKeyBinding(keySetName, event)
693 if binding:
694 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500695 # Otherwise return default in keyBindings.
696 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400697 warning = (
698 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
699 ' problem retrieving key binding for event %r\n'
700 ' from key set %r.\n'
701 ' returning default value: %r' %
702 (event, keySetName, keyBindings[event])
703 )
704 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000705 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000706
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400707 def GetExtraHelpSourceList(self, configSet):
708 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000709
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000710 Valid configSets are 'user' or 'default'. Return a list of tuples of
711 the form (menu_item , path_to_help_file , option), or return the empty
712 list. 'option' is the sequence number of the help resource. 'option'
713 values determine the position of the menu items on the Help menu,
714 therefore the returned list must be sorted by 'option'.
715
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000716 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400717 helpSources = []
718 if configSet == 'user':
719 cfgParser = self.userCfg['main']
720 elif configSet == 'default':
721 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000722 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000723 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000724 options=cfgParser.GetOptionList('HelpFiles')
725 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400726 value=cfgParser.Get('HelpFiles', option, default=';')
727 if value.find(';') == -1: #malformed config entry with no ';'
728 menuItem = '' #make these empty
729 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000730 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000731 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000732 menuItem=value[0].strip()
733 helpPath=value[1].strip()
734 if menuItem and helpPath: #neither are empty strings
735 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000736 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000737 return helpSources
738
739 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400740 """Return a list of the details of all additional help sources.
741
742 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000743 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400744 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000745 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000746 return allHelpSources
747
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400748 def GetFont(self, root, configType, section):
749 """Retrieve a font from configuration (font, font-size, font-bold)
750 Intercept the special value 'TkFixedFont' and substitute
751 the actual font, factoring in some tweaks if needed for
752 appearance sakes.
753
754 The 'root' parameter can normally be any valid Tkinter widget.
755
756 Return a tuple (family, size, weight) suitable for passing
757 to tkinter.Font
758 """
759 family = self.GetOption(configType, section, 'font', default='courier')
760 size = self.GetOption(configType, section, 'font-size', type='int',
761 default='10')
762 bold = self.GetOption(configType, section, 'font-bold', default=0,
763 type='bool')
764 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400765 f = Font(name='TkFixedFont', exists=True, root=root)
766 actualFont = Font.actual(f)
767 family = actualFont['family']
768 size = actualFont['size']
769 if size <= 0:
770 size = 10 # if font in pixels, ignore actual size
771 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400772 return (family, size, 'bold' if bold else 'normal')
773
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000774 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400775 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000776 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000777 self.defaultCfg[key].Load()
778 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000779
780 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400781 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000782 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000783 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000784
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000785
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400786idleConf = IdleConf()
787
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400788_warned = set()
789def _warn(msg, *key):
790 key = (msg,) + key
791 if key not in _warned:
792 try:
793 print(msg, file=sys.stderr)
794 except OSError:
795 pass
796 _warned.add(key)
797
798
terryjreedy349abd92017-07-07 16:00:57 -0400799class ConfigChanges(dict):
800 """Manage a user's proposed configuration option changes.
801
802 Names used across multiple methods:
803 page -- one of the 4 top-level dicts representing a
804 .idlerc/config-x.cfg file.
805 config_type -- name of a page.
806 section -- a section within a page/file.
807 option -- name of an option within a section.
808 value -- value for the option.
809
810 Methods
811 add_option: Add option and value to changes.
812 save_option: Save option and value to config parser.
813 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400814 delete_section: If section exists,
815 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400816 clear: Clear all changes by clearing each page.
817 """
818 def __init__(self):
819 "Create a page for each configuration file"
820 self.pages = [] # List of unhashable dicts.
821 for config_type in idleConf.config_types:
822 self[config_type] = {}
823 self.pages.append(self[config_type])
824
825 def add_option(self, config_type, section, item, value):
826 "Add item/value pair for config_type and section."
827 page = self[config_type]
828 value = str(value) # Make sure we use a string.
829 if section not in page:
830 page[section] = {}
831 page[section][item] = value
832
833 @staticmethod
834 def save_option(config_type, section, item, value):
835 """Return True if the configuration value was added or changed.
836
837 Helper for save_all.
838 """
839 if idleConf.defaultCfg[config_type].has_option(section, item):
840 if idleConf.defaultCfg[config_type].Get(section, item) == value:
841 # The setting equals a default setting, remove it from user cfg.
842 return idleConf.userCfg[config_type].RemoveOption(section, item)
843 # If we got here, set the option.
844 return idleConf.userCfg[config_type].SetOption(section, item, value)
845
846 def save_all(self):
847 """Save configuration changes to the user config file.
848
Louie Lu50c94352017-07-13 02:05:32 +0800849 Clear self in preparation for additional changes.
850 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400851 """
852 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800853
854 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400855 for config_type in self:
856 cfg_type_changed = False
857 page = self[config_type]
858 for section in page:
859 if section == 'HelpFiles': # Remove it for replacement.
860 idleConf.userCfg['main'].remove_section('HelpFiles')
861 cfg_type_changed = True
862 for item, value in page[section].items():
863 if self.save_option(config_type, section, item, value):
864 cfg_type_changed = True
865 if cfg_type_changed:
866 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800867 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400868 for config_type in ['keys', 'highlight']:
869 # Save these even if unchanged!
870 idleConf.userCfg[config_type].Save()
871 self.clear()
872 # ConfigDialog caller must add the following call
873 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800874 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400875
876 def delete_section(self, config_type, section):
877 """Delete a section from self, userCfg, and file.
878
879 Used to delete custom themes and keysets.
880 """
881 if section in self[config_type]:
882 del self[config_type][section]
883 configpage = idleConf.userCfg[config_type]
884 configpage.remove_section(section)
885 configpage.Save()
886
887 def clear(self):
888 """Clear all 4 pages.
889
890 Called in save_all after saving to idleConf.
891 XXX Mark window *title* when there are changes; unmark here.
892 """
893 for page in self.pages:
894 page.clear()
895
896
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400897# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400898def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400899 from zlib import crc32
900 line, crc = 0, 0
901
902 def sprint(obj):
903 global line, crc
904 txt = str(obj)
905 line += 1
906 crc = crc32(txt.encode(encoding='utf-8'), crc)
907 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400908 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400909
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000910 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400911 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400912 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400913 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400914 sprint(key)
915 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000916 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400917 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400918 sprint(section)
919 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000920 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400921 sprint(option + ' = ' + cfg[key].Get(section, option))
922
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000923 dumpCfg(idleConf.defaultCfg)
924 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400925 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400926
927if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400928 from unittest import main
929 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
930
931 # Run revised _dump() as htest?