blob: 12113c19c08672a9d755dc5a4086c472d0e3b74e [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
Xtreakd9677f32019-06-03 09:51:15 +053015to values. For 'main' and 'extensions', user values override
Terry Jan Reedyf46b7822016-11-07 17:15:01 -050016default 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
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000037class InvalidTheme(Exception): pass
38
Steven M. Gavac11ccf32001-09-24 09:43:17 +000039class IdleConfParser(ConfigParser):
40 """
41 A ConfigParser specialised for idle configuration file handling
42 """
43 def __init__(self, cfgFile, cfgDefaults=None):
44 """
45 cfgFile - string, fully specified configuration file name
46 """
terryjreedy349abd92017-07-07 16:00:57 -040047 self.file = cfgFile # This is currently '' when testing.
Serhiy Storchaka89953002013-02-07 15:24:36 +020048 ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000049
Thomas Wouterscf297e42007-02-23 15:07:44 +000050 def Get(self, section, option, type=None, default=None, raw=False):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000051 """
52 Get an option value for given section/option or return default.
53 If type is specified, return as type.
54 """
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040055 # TODO Use default as fallback, at least if not None
56 # Should also print Warning(file, section, option).
57 # Currently may raise ValueError
Thomas Wouterscf297e42007-02-23 15:07:44 +000058 if not self.has_option(section, option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000059 return default
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040060 if type == 'bool':
Thomas Wouterscf297e42007-02-23 15:07:44 +000061 return self.getboolean(section, option)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040062 elif type == 'int':
Thomas Wouterscf297e42007-02-23 15:07:44 +000063 return self.getint(section, option)
64 else:
65 return self.get(section, option, raw=raw)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000066
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040067 def GetOptionList(self, section):
68 "Return a list of options for given section, else []."
Steven M. Gava085eb1b2002-02-05 04:52:32 +000069 if self.has_section(section):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000070 return self.options(section)
71 else: #return a default value
72 return []
73
Steven M. Gavac11ccf32001-09-24 09:43:17 +000074 def Load(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040075 "Load the configuration file from disk."
terryjreedy349abd92017-07-07 16:00:57 -040076 if self.file:
77 self.read(self.file)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000078
Steven M. Gavac11ccf32001-09-24 09:43:17 +000079class IdleUserConfParser(IdleConfParser):
80 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000081 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000082 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000083
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040084 def SetOption(self, section, option, value):
85 """Return True if option is added or changed to value, else False.
86
87 Add section if required. False means option already had value.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000088 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040089 if self.has_option(section, option):
90 if self.get(section, option) == value:
91 return False
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000092 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040093 self.set(section, option, value)
94 return True
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000095 else:
96 if not self.has_section(section):
97 self.add_section(section)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040098 self.set(section, option, value)
99 return True
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000100
Louie Lu50c94352017-07-13 02:05:32 +0800101 def RemoveOption(self, section, option):
102 """Return True if option is removed from section, else False.
103
104 False if either section does not exist or did not have option.
105 """
106 if self.has_section(section):
107 return self.remove_option(section, option)
108 return False
109
110 def AddSection(self, section):
111 "If section doesn't exist, add it."
112 if not self.has_section(section):
113 self.add_section(section)
114
115 def RemoveEmptySections(self):
116 "Remove any sections that have no options."
117 for section in self.sections():
118 if not self.GetOptionList(section):
119 self.remove_section(section)
120
121 def IsEmpty(self):
122 "Return True if no sections after removing empty sections."
123 self.RemoveEmptySections()
124 return not self.sections()
125
Steven M. Gavab77d3432002-03-02 07:16:21 +0000126 def RemoveFile(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400127 "Remove user config file self.file from disk if it exists."
Steven M. Gavab77d3432002-03-02 07:16:21 +0000128 if os.path.exists(self.file):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000129 os.remove(self.file)
130
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000131 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000132 """Update user configuration file.
133
terryjreedy349abd92017-07-07 16:00:57 -0400134 If self not empty after removing empty sections, write the file
135 to disk. Otherwise, remove the file from disk if it exists.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000136
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000137 """
terryjreedy349abd92017-07-07 16:00:57 -0400138 fname = self.file
139 if fname:
140 if not self.IsEmpty():
141 try:
142 cfgFile = open(fname, 'w')
143 except OSError:
144 os.unlink(fname)
145 cfgFile = open(fname, 'w')
146 with cfgFile:
147 self.write(cfgFile)
148 else:
149 self.RemoveFile()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000150
151class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400152 """Hold config parsers for all idle config files in singleton instance.
153
154 Default config files, self.defaultCfg --
155 for config_type in self.config_types:
156 (idle install dir)/config-{config-type}.def
157
158 User config files, self.userCfg --
159 for config_type in self.config_types:
160 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000161 """
Louie Luf776eb02017-07-19 05:17:56 +0800162 def __init__(self, _utest=False):
terryjreedy349abd92017-07-07 16:00:57 -0400163 self.config_types = ('main', 'highlight', 'keys', 'extensions')
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400164 self.defaultCfg = {}
165 self.userCfg = {}
166 self.cfg = {} # TODO use to select userCfg vs defaultCfg
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400167
Louie Luf776eb02017-07-19 05:17:56 +0800168 if not _utest:
169 self.CreateConfigHandlers()
170 self.LoadCfgFiles()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000171
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000172 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400173 "Populate default and user config parser dictionaries."
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000174 #build idle install path
175 if __name__ != '__main__': # we were imported
terryjreedy223c7e72017-07-07 22:28:06 -0400176 idleDir = os.path.dirname(__file__)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000177 else: # we were exec'ed (for testing only)
terryjreedy223c7e72017-07-07 22:28:06 -0400178 idleDir = os.path.abspath(sys.path[0])
179 self.userdir = userDir = self.GetUserCfgDir()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400180
181 defCfgFiles = {}
182 usrCfgFiles = {}
183 # TODO eliminate these temporaries by combining loops
184 for cfgType in self.config_types: #build config file names
185 defCfgFiles[cfgType] = os.path.join(
186 idleDir, 'config-' + cfgType + '.def')
187 usrCfgFiles[cfgType] = os.path.join(
188 userDir, 'config-' + cfgType + '.cfg')
189 for cfgType in self.config_types: #create config parsers
190 self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
191 self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000192
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000193 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400194 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000195
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400196 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000197 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000198 cfgDir = '.idlerc'
199 userDir = os.path.expanduser('~')
200 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000201 if not os.path.exists(userDir):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400202 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
203 userDir + ',\n but the path does not exist.')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000204 try:
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400205 print(warn, file=sys.stderr)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200206 except OSError:
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000207 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000208 userDir = '~'
209 if userDir == "~": # still no path to home!
210 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
211 userDir = os.getcwd()
212 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000213 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000214 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000215 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200216 except OSError:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400217 warn = ('\n Warning: unable to create user config directory\n' +
218 userDir + '\n Check path and permissions.\n Exiting!\n')
Louie Luf776eb02017-07-19 05:17:56 +0800219 if not idlelib.testing:
220 print(warn, file=sys.stderr)
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000221 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400222 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000223 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000224
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000225 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000226 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400227 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000228
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400229 If type is not None, return a value of that type. Also pass raw
230 to the config parser. First try to return a valid value
231 (including type) from a user configuration. If that fails, try
232 the default configuration. If that fails, return default, with a
233 default of None.
234
235 Warn if either user or default configurations have an invalid value.
236 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000237 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200238 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400239 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200240 return self.userCfg[configType].Get(section, option,
241 type=type, raw=raw)
242 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400243 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200244 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400245 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200246 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400247 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400248 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200249 try:
250 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400251 return self.defaultCfg[configType].Get(
252 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200253 except ValueError:
254 pass
255 #returning default, print warning
256 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400257 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200258 ' problem retrieving configuration option %r\n'
259 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400260 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200261 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400262 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200263 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000264
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000265 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400266 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000267 self.userCfg[configType].SetOption(section, option, value)
268
Steven M. Gava2a63a072001-10-26 06:50:54 +0000269 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400270 """Return sections for configSet configType configuration.
271
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000272 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400273 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000274 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400275 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000276 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000277 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400278 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000279 elif configSet == 'default':
280 cfgParser=self.defaultCfg[configType]
281 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000282 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000283 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000284
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400285 def GetHighlight(self, theme, element):
286 """Return dict of theme element highlight colors.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400287
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400288 The keys are 'foreground' and 'background'. The values are
289 tkinter color strings for configuring backgrounds and tags.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000290 """
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400291 cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
292 else 'user')
293 theme_dict = self.GetThemeDict(cfg, theme)
294 fore = theme_dict[element + '-foreground']
295 if element == 'cursor':
296 element = 'normal'
297 back = theme_dict[element + '-background']
298 return {"foreground": fore, "background": back}
Steven M. Gava9f25e672002-02-11 02:51:18 +0000299
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400300 def GetThemeDict(self, type, themeName):
301 """Return {option:value} dict for elements in themeName.
302
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000303 type - string, 'default' or 'user' theme type
304 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400305 Values are loaded over ultimate fallback defaults to guarantee
306 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000307 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000308 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400309 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000310 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400311 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000312 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000313 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400314 # Provide foreground and background colors for each theme
315 # element (other than cursor) even though some values are not
316 # yet used by idle, to allow for their use in the future.
317 # Default values are generally black and white.
318 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400319 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000320 'normal-background':'#ffffff',
321 'keyword-foreground':'#000000',
322 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000323 'builtin-foreground':'#000000',
324 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000325 'comment-foreground':'#000000',
326 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000327 'string-foreground':'#000000',
328 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000329 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000330 'definition-background':'#ffffff',
331 'hilite-foreground':'#000000',
332 'hilite-background':'gray',
333 'break-foreground':'#ffffff',
334 'break-background':'#000000',
335 'hit-foreground':'#ffffff',
336 'hit-background':'#000000',
337 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000338 'error-background':'#000000',
339 #cursor (only foreground can be set)
340 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000341 #shell window
342 'stdout-foreground':'#000000',
343 'stdout-background':'#ffffff',
344 'stderr-foreground':'#000000',
345 'stderr-background':'#ffffff',
346 'console-foreground':'#000000',
wohlganger58fc71c2017-09-10 16:19:47 -0500347 'console-background':'#ffffff',
Cheryl Sabellade651622018-06-01 21:45:54 -0400348 'context-foreground':'#000000',
349 'context-background':'#ffffff',
wohlganger58fc71c2017-09-10 16:19:47 -0500350 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000351 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400352 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400353 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400354 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000355 ' -\n problem retrieving theme element %r'
356 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400357 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000358 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400359 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400360 theme[element] = cfgParser.Get(
361 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000362 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000363
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000364 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400365 "Return the name of the currently active text color theme."
366 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500367
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400368 def CurrentKeys(self):
369 """Return the name of the currently active key set."""
370 return self.current_colors_and_keys('Keys')
371
372 def current_colors_and_keys(self, section):
373 """Return the currently active name for Theme or Keys section.
374
375 idlelib.config-main.def ('default') includes these sections
376
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500377 [Theme]
378 default= 1
379 name= IDLE Classic
380 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500381
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400382 [Keys]
383 default= 1
384 name=
385 name2=
386
387 Item 'name2', is used for built-in ('default') themes and keys
388 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
389 because setting 'name' to a builtin not defined in older IDLEs
390 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500391 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400392 When default = True, 'name2' takes precedence over 'name',
393 while older IDLEs will just use name. When default = False,
394 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500395 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400396 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400397 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500398 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400399 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500400 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400401 name = self.GetOption('main', section, 'name2', default='')
402 if not name:
403 name = self.GetOption('main', section, 'name', default='')
404 if name:
405 source = self.defaultCfg if default else self.userCfg
406 if source[cfgname].has_section(name):
407 return name
408 return "IDLE Classic" if section == 'Theme' else self.default_keys()
409
410 @staticmethod
411 def default_keys():
412 if sys.platform[:3] == 'win':
413 return 'IDLE Classic Windows'
414 elif sys.platform == 'darwin':
415 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500416 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400417 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000418
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400419 def GetExtensions(self, active_only=True,
420 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400421 """Return extensions in default and user config-extensions files.
422
423 If active_only True, only return active (enabled) extensions
424 and optionally only editor or shell extensions.
425 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000426 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400427 extns = self.RemoveKeyBindNames(
428 self.GetSectionList('default', 'extensions'))
429 userExtns = self.RemoveKeyBindNames(
430 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000431 for extn in userExtns:
432 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000433 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500434 for extn in ('AutoComplete','CodeContext',
435 'FormatParagraph','ParenMatch'):
436 extns.remove(extn)
437 # specific exclusions because we are storing config for mainlined old
438 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000439 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400440 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000441 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000442 if self.GetOption('extensions', extn, 'enable', default=True,
443 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000444 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400445 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000446 if editor_only:
447 option = "enable_editor"
448 else:
449 option = "enable_shell"
450 if self.GetOption('extensions', extn,option,
451 default=True, type='bool',
452 warn_on_default=False):
453 activeExtns.append(extn)
454 else:
455 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000456 return activeExtns
457 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000458 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000459
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400460 def RemoveKeyBindNames(self, extnNameList):
461 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800462 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000463
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400464 def GetExtnNameForEvent(self, virtualEvent):
465 """Return the name of the extension binding virtualEvent, or None.
466
467 virtualEvent - string, name of the virtual event to test for,
468 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000469 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400470 extName = None
471 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000472 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000473 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000474 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400475 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000476 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000477
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400478 def GetExtensionKeys(self, extensionName):
479 """Return dict: {configurable extensionName event : active keybinding}.
480
481 Events come from default config extension_cfgBindings section.
482 Keybindings come from GetCurrentKeySet() active key dict,
483 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000484 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400485 keysName = extensionName + '_cfgBindings'
486 activeKeys = self.GetCurrentKeySet()
487 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000488 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400489 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000490 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400491 event = '<<' + eventName + '>>'
492 binding = activeKeys[event]
493 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000494 return extKeys
495
Steven M. Gavac628a062002-01-19 10:33:21 +0000496 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400497 """Return dict {configurable extensionName event : keybinding list}.
498
499 Events come from default config extension_cfgBindings section.
500 Keybindings list come from the splitting of GetOption, which
501 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000502 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400503 keysName = extensionName+'_cfgBindings'
504 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000505 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400506 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000507 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400508 binding = self.GetOption(
509 'extensions', keysName, eventName, default='').split()
510 event = '<<' + eventName + '>>'
511 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000512 return extKeys
513
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400514 def GetExtensionBindings(self, extensionName):
515 """Return dict {extensionName event : active or defined keybinding}.
516
517 Augment self.GetExtensionKeys(extensionName) with mapping of non-
518 configurable events (from default config) to GetOption splits,
519 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000520 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400521 bindsName = extensionName + '_bindings'
522 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000523 #add the non-configurable bindings
524 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400525 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000526 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400527 binding = self.GetOption(
528 'extensions', bindsName, eventName, default='').split()
529 event = '<<' + eventName + '>>'
530 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000531
532 return extBinds
533
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000534 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400535 """Return the keybinding list for keySetName eventStr.
536
537 keySetName - name of key binding set (config-keys section).
538 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000539 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400540 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400541 binding = self.GetOption('keys', keySetName, eventName, default='',
542 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000543 return binding
544
Steven M. Gavac628a062002-01-19 10:33:21 +0000545 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400546 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 result = self.GetKeySet(self.CurrentKeys())
548
Ned Deilyb7601672014-03-27 20:49:14 -0700549 if sys.platform == "darwin":
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500550 # macOS (OS X) Tk variants do not support the "Alt"
551 # keyboard modifier. Replace it with "Option".
552 # TODO (Ned?): the "Option" modifier does not work properly
553 # for Cocoa Tk and XQuartz Tk so we should not use it
554 # in the default 'OSX' keyset.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000555 for k, v in result.items():
556 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
557 if v != v2:
558 result[k] = v2
559
560 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000561
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400562 def GetKeySet(self, keySetName):
563 """Return event-key dict for keySetName core plus active extensions.
564
565 If a binding defined in an extension is already in use, the
566 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000567 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400568 keySet = self.GetCoreKeys(keySetName)
569 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000570 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400571 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000572 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000573 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000574 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000575 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400576 extKeys[event] = '' #disable this binding
577 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000578 return keySet
579
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400580 def IsCoreBinding(self, virtualEvent):
581 """Return True if the virtual event is one of the core idle key events.
582
583 virtualEvent - string, name of the virtual event to test for,
584 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000585 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000586 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000587
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400588# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500589# and copied in function below.
590
591 former_extension_events = { # Those with user-configurable keys.
592 '<<force-open-completions>>', '<<expand-word>>',
593 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
594 '<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400595
Steven M. Gavac628a062002-01-19 10:33:21 +0000596 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400597 """Return dict of core virtual-key keybindings for keySetName.
598
599 The default keySetName None corresponds to the keyBindings base
600 dict. If keySetName is not None, bindings from the config
601 file(s) are loaded _over_ these defaults, so if there is a
602 problem getting any core binding there will be an 'ultimate last
603 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000604 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000605 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000606 '<<copy>>': ['<Control-c>', '<Control-C>'],
607 '<<cut>>': ['<Control-x>', '<Control-X>'],
608 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000609 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
610 '<<center-insert>>': ['<Control-l>'],
611 '<<close-all-windows>>': ['<Control-q>'],
612 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000613 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000614 '<<end-of-file>>': ['<Control-d>'],
615 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000616 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000617 '<<history-next>>': ['<Alt-n>'],
618 '<<history-previous>>': ['<Alt-p>'],
619 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000620 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000621 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000622 '<<open-class-browser>>': ['<Alt-c>'],
623 '<<open-module>>': ['<Alt-m>'],
624 '<<open-new-window>>': ['<Control-n>'],
625 '<<open-window-from-file>>': ['<Control-o>'],
626 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000627 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000628 '<<redo>>': ['<Control-y>'],
629 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000630 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000631 '<<save-window-as-file>>': ['<Alt-s>'],
632 '<<save-window>>': ['<Control-s>'],
633 '<<select-all>>': ['<Alt-a>'],
634 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000635 '<<undo>>': ['<Control-z>'],
636 '<<find-again>>': ['<Control-g>', '<F3>'],
637 '<<find-in-files>>': ['<Alt-F3>'],
638 '<<find-selection>>': ['<Control-F3>'],
639 '<<find>>': ['<Control-f>'],
640 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000641 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000642 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300643 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000644 '<<smart-indent>>': ['<Key-Tab>'],
645 '<<indent-region>>': ['<Control-Key-bracketright>'],
646 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
647 '<<comment-region>>': ['<Alt-Key-3>'],
648 '<<uncomment-region>>': ['<Alt-Key-4>'],
649 '<<tabify-region>>': ['<Alt-Key-5>'],
650 '<<untabify-region>>': ['<Alt-Key-6>'],
651 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000652 '<<change-indentwidth>>': ['<Alt-Key-u>'],
653 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500654 '<<del-word-right>>': ['<Control-Key-Delete>'],
655 '<<force-open-completions>>': ['<Control-Key-space>'],
656 '<<expand-word>>': ['<Alt-Key-slash>'],
657 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
658 '<<flash-paren>>': ['<Control-Key-0>'],
659 '<<format-paragraph>>': ['<Alt-Key-q>'],
660 '<<run-module>>': ['<Key-F5>'],
661 '<<check-module>>': ['<Alt-Key-x>'],
662 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000663 }
wohlganger58fc71c2017-09-10 16:19:47 -0500664
Steven M. Gava17d01542001-12-03 00:37:28 +0000665 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400666 if not (self.userCfg['keys'].has_section(keySetName) or
667 self.defaultCfg['keys'].has_section(keySetName)):
668 warning = (
669 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
670 ' key set %r is not defined, using default bindings.' %
671 (keySetName,)
672 )
673 _warn(warning, 'keys', keySetName)
674 else:
675 for event in keyBindings:
676 binding = self.GetKeyBinding(keySetName, event)
677 if binding:
678 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500679 # Otherwise return default in keyBindings.
680 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400681 warning = (
682 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
683 ' problem retrieving key binding for event %r\n'
684 ' from key set %r.\n'
685 ' returning default value: %r' %
686 (event, keySetName, keyBindings[event])
687 )
688 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000689 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000690
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400691 def GetExtraHelpSourceList(self, configSet):
692 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000693
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000694 Valid configSets are 'user' or 'default'. Return a list of tuples of
695 the form (menu_item , path_to_help_file , option), or return the empty
696 list. 'option' is the sequence number of the help resource. 'option'
697 values determine the position of the menu items on the Help menu,
698 therefore the returned list must be sorted by 'option'.
699
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000700 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400701 helpSources = []
702 if configSet == 'user':
703 cfgParser = self.userCfg['main']
704 elif configSet == 'default':
705 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000706 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000707 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000708 options=cfgParser.GetOptionList('HelpFiles')
709 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400710 value=cfgParser.Get('HelpFiles', option, default=';')
711 if value.find(';') == -1: #malformed config entry with no ';'
712 menuItem = '' #make these empty
713 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000714 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000715 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000716 menuItem=value[0].strip()
717 helpPath=value[1].strip()
718 if menuItem and helpPath: #neither are empty strings
719 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000720 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000721 return helpSources
722
723 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400724 """Return a list of the details of all additional help sources.
725
726 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000727 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400728 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000729 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000730 return allHelpSources
731
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400732 def GetFont(self, root, configType, section):
733 """Retrieve a font from configuration (font, font-size, font-bold)
734 Intercept the special value 'TkFixedFont' and substitute
735 the actual font, factoring in some tweaks if needed for
736 appearance sakes.
737
738 The 'root' parameter can normally be any valid Tkinter widget.
739
740 Return a tuple (family, size, weight) suitable for passing
741 to tkinter.Font
742 """
743 family = self.GetOption(configType, section, 'font', default='courier')
744 size = self.GetOption(configType, section, 'font-size', type='int',
745 default='10')
746 bold = self.GetOption(configType, section, 'font-bold', default=0,
747 type='bool')
748 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400749 f = Font(name='TkFixedFont', exists=True, root=root)
750 actualFont = Font.actual(f)
751 family = actualFont['family']
752 size = actualFont['size']
753 if size <= 0:
754 size = 10 # if font in pixels, ignore actual size
755 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400756 return (family, size, 'bold' if bold else 'normal')
757
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000758 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400759 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000760 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761 self.defaultCfg[key].Load()
762 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000763
764 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400765 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000766 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000767 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000768
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000769
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400770idleConf = IdleConf()
771
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400772_warned = set()
773def _warn(msg, *key):
774 key = (msg,) + key
775 if key not in _warned:
776 try:
777 print(msg, file=sys.stderr)
778 except OSError:
779 pass
780 _warned.add(key)
781
782
terryjreedy349abd92017-07-07 16:00:57 -0400783class ConfigChanges(dict):
784 """Manage a user's proposed configuration option changes.
785
786 Names used across multiple methods:
787 page -- one of the 4 top-level dicts representing a
788 .idlerc/config-x.cfg file.
789 config_type -- name of a page.
790 section -- a section within a page/file.
791 option -- name of an option within a section.
792 value -- value for the option.
793
794 Methods
795 add_option: Add option and value to changes.
796 save_option: Save option and value to config parser.
797 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400798 delete_section: If section exists,
799 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400800 clear: Clear all changes by clearing each page.
801 """
802 def __init__(self):
803 "Create a page for each configuration file"
804 self.pages = [] # List of unhashable dicts.
805 for config_type in idleConf.config_types:
806 self[config_type] = {}
807 self.pages.append(self[config_type])
808
809 def add_option(self, config_type, section, item, value):
810 "Add item/value pair for config_type and section."
811 page = self[config_type]
812 value = str(value) # Make sure we use a string.
813 if section not in page:
814 page[section] = {}
815 page[section][item] = value
816
817 @staticmethod
818 def save_option(config_type, section, item, value):
819 """Return True if the configuration value was added or changed.
820
821 Helper for save_all.
822 """
823 if idleConf.defaultCfg[config_type].has_option(section, item):
824 if idleConf.defaultCfg[config_type].Get(section, item) == value:
825 # The setting equals a default setting, remove it from user cfg.
826 return idleConf.userCfg[config_type].RemoveOption(section, item)
827 # If we got here, set the option.
828 return idleConf.userCfg[config_type].SetOption(section, item, value)
829
830 def save_all(self):
831 """Save configuration changes to the user config file.
832
Louie Lu50c94352017-07-13 02:05:32 +0800833 Clear self in preparation for additional changes.
834 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400835 """
836 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800837
838 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400839 for config_type in self:
840 cfg_type_changed = False
841 page = self[config_type]
842 for section in page:
843 if section == 'HelpFiles': # Remove it for replacement.
844 idleConf.userCfg['main'].remove_section('HelpFiles')
845 cfg_type_changed = True
846 for item, value in page[section].items():
847 if self.save_option(config_type, section, item, value):
848 cfg_type_changed = True
849 if cfg_type_changed:
850 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800851 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400852 for config_type in ['keys', 'highlight']:
853 # Save these even if unchanged!
854 idleConf.userCfg[config_type].Save()
855 self.clear()
856 # ConfigDialog caller must add the following call
857 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800858 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400859
860 def delete_section(self, config_type, section):
861 """Delete a section from self, userCfg, and file.
862
863 Used to delete custom themes and keysets.
864 """
865 if section in self[config_type]:
866 del self[config_type][section]
867 configpage = idleConf.userCfg[config_type]
868 configpage.remove_section(section)
869 configpage.Save()
870
871 def clear(self):
872 """Clear all 4 pages.
873
874 Called in save_all after saving to idleConf.
875 XXX Mark window *title* when there are changes; unmark here.
876 """
877 for page in self.pages:
878 page.clear()
879
880
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400881# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400882def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400883 from zlib import crc32
884 line, crc = 0, 0
885
886 def sprint(obj):
887 global line, crc
888 txt = str(obj)
889 line += 1
890 crc = crc32(txt.encode(encoding='utf-8'), crc)
891 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400892 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400893
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000894 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400895 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400896 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400897 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400898 sprint(key)
899 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000900 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400901 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400902 sprint(section)
903 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000904 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400905 sprint(option + ' = ' + cfg[key].Get(section, option))
906
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000907 dumpCfg(idleConf.defaultCfg)
908 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400909 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400910
911if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400912 from unittest import main
913 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
914
915 # Run revised _dump() as htest?