blob: 04444a3bf20b30bc250764203ced633cab2462b3 [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. Gavac11ccf32001-09-24 09:43:17 +0000126 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000127 """Update user configuration file.
128
terryjreedy349abd92017-07-07 16:00:57 -0400129 If self not empty after removing empty sections, write the file
130 to disk. Otherwise, remove the file from disk if it exists.
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000131 """
terryjreedy349abd92017-07-07 16:00:57 -0400132 fname = self.file
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400133 if fname and fname[0] != '#':
terryjreedy349abd92017-07-07 16:00:57 -0400134 if not self.IsEmpty():
135 try:
136 cfgFile = open(fname, 'w')
137 except OSError:
138 os.unlink(fname)
139 cfgFile = open(fname, 'w')
140 with cfgFile:
141 self.write(cfgFile)
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400142 elif os.path.exists(self.file):
143 os.remove(self.file)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000144
145class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400146 """Hold config parsers for all idle config files in singleton instance.
147
148 Default config files, self.defaultCfg --
149 for config_type in self.config_types:
150 (idle install dir)/config-{config-type}.def
151
152 User config files, self.userCfg --
153 for config_type in self.config_types:
154 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000155 """
Louie Luf776eb02017-07-19 05:17:56 +0800156 def __init__(self, _utest=False):
terryjreedy349abd92017-07-07 16:00:57 -0400157 self.config_types = ('main', 'highlight', 'keys', 'extensions')
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400158 self.defaultCfg = {}
159 self.userCfg = {}
160 self.cfg = {} # TODO use to select userCfg vs defaultCfg
Zackery Spytz9c284492019-11-13 00:13:33 -0700161 # self.blink_off_time = <first editor text>['insertofftime']
162 # See https:/bugs.python.org/issue4630, msg356516.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400163
Louie Luf776eb02017-07-19 05:17:56 +0800164 if not _utest:
165 self.CreateConfigHandlers()
166 self.LoadCfgFiles()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000167
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000168 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400169 "Populate default and user config parser dictionaries."
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400170 idledir = os.path.dirname(__file__)
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400171 self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir()
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400172 for cfg_type in self.config_types:
173 self.defaultCfg[cfg_type] = IdleConfParser(
174 os.path.join(idledir, f'config-{cfg_type}.def'))
175 self.userCfg[cfg_type] = IdleUserConfParser(
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400176 os.path.join(userdir or '#', f'config-{cfg_type}.cfg'))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000177
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000178 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400179 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000180
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400181 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000182 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000183 cfgDir = '.idlerc'
184 userDir = os.path.expanduser('~')
185 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000186 if not os.path.exists(userDir):
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400187 if not idlelib.testing:
188 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
189 userDir + ',\n but the path does not exist.')
190 try:
191 print(warn, file=sys.stderr)
192 except OSError:
193 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000194 userDir = '~'
195 if userDir == "~": # still no path to home!
196 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
197 userDir = os.getcwd()
198 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000199 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000200 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000201 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200202 except OSError:
Louie Luf776eb02017-07-19 05:17:56 +0800203 if not idlelib.testing:
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400204 warn = ('\n Warning: unable to create user config directory\n' +
205 userDir + '\n Check path and permissions.\n Exiting!\n')
206 try:
207 print(warn, file=sys.stderr)
208 except OSError:
209 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000210 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400211 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000212 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000213
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000214 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000215 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400216 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000217
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400218 If type is not None, return a value of that type. Also pass raw
219 to the config parser. First try to return a valid value
220 (including type) from a user configuration. If that fails, try
221 the default configuration. If that fails, return default, with a
222 default of None.
223
224 Warn if either user or default configurations have an invalid value.
225 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000226 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200227 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400228 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200229 return self.userCfg[configType].Get(section, option,
230 type=type, raw=raw)
231 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400232 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200233 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400234 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200235 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400236 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400237 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200238 try:
239 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400240 return self.defaultCfg[configType].Get(
241 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200242 except ValueError:
243 pass
244 #returning default, print warning
245 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400246 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200247 ' problem retrieving configuration option %r\n'
248 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400249 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200250 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400251 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200252 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000253
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000254 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400255 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000256 self.userCfg[configType].SetOption(section, option, value)
257
Steven M. Gava2a63a072001-10-26 06:50:54 +0000258 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400259 """Return sections for configSet configType configuration.
260
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000261 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400262 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000263 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400264 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000265 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000266 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400267 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000268 elif configSet == 'default':
269 cfgParser=self.defaultCfg[configType]
270 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000271 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000272 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000273
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400274 def GetHighlight(self, theme, element):
275 """Return dict of theme element highlight colors.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400276
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400277 The keys are 'foreground' and 'background'. The values are
278 tkinter color strings for configuring backgrounds and tags.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000279 """
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400280 cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
281 else 'user')
282 theme_dict = self.GetThemeDict(cfg, theme)
283 fore = theme_dict[element + '-foreground']
284 if element == 'cursor':
285 element = 'normal'
286 back = theme_dict[element + '-background']
287 return {"foreground": fore, "background": back}
Steven M. Gava9f25e672002-02-11 02:51:18 +0000288
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400289 def GetThemeDict(self, type, themeName):
290 """Return {option:value} dict for elements in themeName.
291
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000292 type - string, 'default' or 'user' theme type
293 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400294 Values are loaded over ultimate fallback defaults to guarantee
295 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000296 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000297 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400298 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000299 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400300 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000301 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000302 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400303 # Provide foreground and background colors for each theme
304 # element (other than cursor) even though some values are not
305 # yet used by idle, to allow for their use in the future.
306 # Default values are generally black and white.
307 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400308 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000309 'normal-background':'#ffffff',
310 'keyword-foreground':'#000000',
311 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000312 'builtin-foreground':'#000000',
313 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000314 'comment-foreground':'#000000',
315 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000316 'string-foreground':'#000000',
317 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000318 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000319 'definition-background':'#ffffff',
320 'hilite-foreground':'#000000',
321 'hilite-background':'gray',
322 'break-foreground':'#ffffff',
323 'break-background':'#000000',
324 'hit-foreground':'#ffffff',
325 'hit-background':'#000000',
326 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000327 'error-background':'#000000',
Tal Einat7123ea02019-07-23 15:22:11 +0300328 'context-foreground':'#000000',
329 'context-background':'#ffffff',
330 'linenumber-foreground':'#000000',
331 'linenumber-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000332 #cursor (only foreground can be set)
333 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000334 #shell window
335 'stdout-foreground':'#000000',
336 'stdout-background':'#ffffff',
337 'stderr-foreground':'#000000',
338 'stderr-background':'#ffffff',
339 'console-foreground':'#000000',
wohlganger58fc71c2017-09-10 16:19:47 -0500340 'console-background':'#ffffff',
341 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000342 for element in theme:
Tal Einat7123ea02019-07-23 15:22:11 +0300343 if not (cfgParser.has_option(themeName, element) or
344 # Skip warning for new elements.
345 element.startswith(('context-', 'linenumber-'))):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400346 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400347 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000348 ' -\n problem retrieving theme element %r'
349 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400350 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000351 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400352 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400353 theme[element] = cfgParser.Get(
354 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000355 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000356
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000357 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400358 "Return the name of the currently active text color theme."
359 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500360
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400361 def CurrentKeys(self):
362 """Return the name of the currently active key set."""
363 return self.current_colors_and_keys('Keys')
364
365 def current_colors_and_keys(self, section):
366 """Return the currently active name for Theme or Keys section.
367
368 idlelib.config-main.def ('default') includes these sections
369
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500370 [Theme]
371 default= 1
372 name= IDLE Classic
373 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500374
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400375 [Keys]
376 default= 1
377 name=
378 name2=
379
380 Item 'name2', is used for built-in ('default') themes and keys
381 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
382 because setting 'name' to a builtin not defined in older IDLEs
383 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500384 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400385 When default = True, 'name2' takes precedence over 'name',
386 while older IDLEs will just use name. When default = False,
387 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500388 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400389 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400390 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500391 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400392 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500393 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400394 name = self.GetOption('main', section, 'name2', default='')
395 if not name:
396 name = self.GetOption('main', section, 'name', default='')
397 if name:
398 source = self.defaultCfg if default else self.userCfg
399 if source[cfgname].has_section(name):
400 return name
401 return "IDLE Classic" if section == 'Theme' else self.default_keys()
402
403 @staticmethod
404 def default_keys():
405 if sys.platform[:3] == 'win':
406 return 'IDLE Classic Windows'
407 elif sys.platform == 'darwin':
408 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500409 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400410 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000411
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400412 def GetExtensions(self, active_only=True,
413 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400414 """Return extensions in default and user config-extensions files.
415
416 If active_only True, only return active (enabled) extensions
417 and optionally only editor or shell extensions.
418 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000419 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400420 extns = self.RemoveKeyBindNames(
421 self.GetSectionList('default', 'extensions'))
422 userExtns = self.RemoveKeyBindNames(
423 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000424 for extn in userExtns:
425 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000426 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500427 for extn in ('AutoComplete','CodeContext',
428 'FormatParagraph','ParenMatch'):
429 extns.remove(extn)
430 # specific exclusions because we are storing config for mainlined old
431 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000432 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400433 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000434 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000435 if self.GetOption('extensions', extn, 'enable', default=True,
436 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000437 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400438 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000439 if editor_only:
440 option = "enable_editor"
441 else:
442 option = "enable_shell"
443 if self.GetOption('extensions', extn,option,
444 default=True, type='bool',
445 warn_on_default=False):
446 activeExtns.append(extn)
447 else:
448 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000449 return activeExtns
450 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000451 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000452
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400453 def RemoveKeyBindNames(self, extnNameList):
454 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800455 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000456
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400457 def GetExtnNameForEvent(self, virtualEvent):
458 """Return the name of the extension binding virtualEvent, or None.
459
460 virtualEvent - string, name of the virtual event to test for,
461 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000462 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400463 extName = None
464 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000465 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000466 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000467 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400468 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000469 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000470
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400471 def GetExtensionKeys(self, extensionName):
472 """Return dict: {configurable extensionName event : active keybinding}.
473
474 Events come from default config extension_cfgBindings section.
475 Keybindings come from GetCurrentKeySet() active key dict,
476 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000477 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400478 keysName = extensionName + '_cfgBindings'
479 activeKeys = self.GetCurrentKeySet()
480 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000481 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400482 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000483 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400484 event = '<<' + eventName + '>>'
485 binding = activeKeys[event]
486 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000487 return extKeys
488
Steven M. Gavac628a062002-01-19 10:33:21 +0000489 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400490 """Return dict {configurable extensionName event : keybinding list}.
491
492 Events come from default config extension_cfgBindings section.
493 Keybindings list come from the splitting of GetOption, which
494 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000495 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400496 keysName = extensionName+'_cfgBindings'
497 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000498 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400499 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000500 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400501 binding = self.GetOption(
502 'extensions', keysName, eventName, default='').split()
503 event = '<<' + eventName + '>>'
504 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000505 return extKeys
506
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400507 def GetExtensionBindings(self, extensionName):
508 """Return dict {extensionName event : active or defined keybinding}.
509
510 Augment self.GetExtensionKeys(extensionName) with mapping of non-
511 configurable events (from default config) to GetOption splits,
512 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000513 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400514 bindsName = extensionName + '_bindings'
515 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000516 #add the non-configurable bindings
517 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400518 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000519 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400520 binding = self.GetOption(
521 'extensions', bindsName, eventName, default='').split()
522 event = '<<' + eventName + '>>'
523 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000524
525 return extBinds
526
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000527 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400528 """Return the keybinding list for keySetName eventStr.
529
530 keySetName - name of key binding set (config-keys section).
531 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000532 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400533 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400534 binding = self.GetOption('keys', keySetName, eventName, default='',
535 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000536 return binding
537
Steven M. Gavac628a062002-01-19 10:33:21 +0000538 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400539 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000540 result = self.GetKeySet(self.CurrentKeys())
541
Ned Deilyb7601672014-03-27 20:49:14 -0700542 if sys.platform == "darwin":
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500543 # macOS (OS X) Tk variants do not support the "Alt"
544 # keyboard modifier. Replace it with "Option".
545 # TODO (Ned?): the "Option" modifier does not work properly
546 # for Cocoa Tk and XQuartz Tk so we should not use it
547 # in the default 'OSX' keyset.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000548 for k, v in result.items():
549 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
550 if v != v2:
551 result[k] = v2
552
553 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000554
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400555 def GetKeySet(self, keySetName):
556 """Return event-key dict for keySetName core plus active extensions.
557
558 If a binding defined in an extension is already in use, the
559 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000560 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400561 keySet = self.GetCoreKeys(keySetName)
562 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000563 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400564 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000565 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000566 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000567 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000568 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400569 extKeys[event] = '' #disable this binding
570 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000571 return keySet
572
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400573 def IsCoreBinding(self, virtualEvent):
574 """Return True if the virtual event is one of the core idle key events.
575
576 virtualEvent - string, name of the virtual event to test for,
577 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000578 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000579 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000580
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400581# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500582# and copied in function below.
583
584 former_extension_events = { # Those with user-configurable keys.
585 '<<force-open-completions>>', '<<expand-word>>',
586 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400587 '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
588 '<<run-custom>>',
589 }
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400590
Steven M. Gavac628a062002-01-19 10:33:21 +0000591 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400592 """Return dict of core virtual-key keybindings for keySetName.
593
594 The default keySetName None corresponds to the keyBindings base
595 dict. If keySetName is not None, bindings from the config
596 file(s) are loaded _over_ these defaults, so if there is a
597 problem getting any core binding there will be an 'ultimate last
598 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000599 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000600 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000601 '<<copy>>': ['<Control-c>', '<Control-C>'],
602 '<<cut>>': ['<Control-x>', '<Control-X>'],
603 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000604 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
605 '<<center-insert>>': ['<Control-l>'],
606 '<<close-all-windows>>': ['<Control-q>'],
607 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000608 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000609 '<<end-of-file>>': ['<Control-d>'],
610 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000611 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000612 '<<history-next>>': ['<Alt-n>'],
613 '<<history-previous>>': ['<Alt-p>'],
614 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000615 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000616 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000617 '<<open-class-browser>>': ['<Alt-c>'],
618 '<<open-module>>': ['<Alt-m>'],
619 '<<open-new-window>>': ['<Control-n>'],
620 '<<open-window-from-file>>': ['<Control-o>'],
621 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000622 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000623 '<<redo>>': ['<Control-y>'],
624 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000625 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000626 '<<save-window-as-file>>': ['<Alt-s>'],
627 '<<save-window>>': ['<Control-s>'],
628 '<<select-all>>': ['<Alt-a>'],
629 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000630 '<<undo>>': ['<Control-z>'],
631 '<<find-again>>': ['<Control-g>', '<F3>'],
632 '<<find-in-files>>': ['<Alt-F3>'],
633 '<<find-selection>>': ['<Control-F3>'],
634 '<<find>>': ['<Control-f>'],
635 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000637 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300638 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000639 '<<smart-indent>>': ['<Key-Tab>'],
640 '<<indent-region>>': ['<Control-Key-bracketright>'],
641 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
642 '<<comment-region>>': ['<Alt-Key-3>'],
643 '<<uncomment-region>>': ['<Alt-Key-4>'],
644 '<<tabify-region>>': ['<Alt-Key-5>'],
645 '<<untabify-region>>': ['<Alt-Key-6>'],
646 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000647 '<<change-indentwidth>>': ['<Alt-Key-u>'],
648 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500649 '<<del-word-right>>': ['<Control-Key-Delete>'],
650 '<<force-open-completions>>': ['<Control-Key-space>'],
651 '<<expand-word>>': ['<Alt-Key-slash>'],
652 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
653 '<<flash-paren>>': ['<Control-Key-0>'],
654 '<<format-paragraph>>': ['<Alt-Key-q>'],
655 '<<run-module>>': ['<Key-F5>'],
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400656 '<<run-custom>>': ['<Shift-Key-F5>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500657 '<<check-module>>': ['<Alt-Key-x>'],
658 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000659 }
wohlganger58fc71c2017-09-10 16:19:47 -0500660
Steven M. Gava17d01542001-12-03 00:37:28 +0000661 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400662 if not (self.userCfg['keys'].has_section(keySetName) or
663 self.defaultCfg['keys'].has_section(keySetName)):
664 warning = (
665 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
666 ' key set %r is not defined, using default bindings.' %
667 (keySetName,)
668 )
669 _warn(warning, 'keys', keySetName)
670 else:
671 for event in keyBindings:
672 binding = self.GetKeyBinding(keySetName, event)
673 if binding:
674 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500675 # Otherwise return default in keyBindings.
676 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400677 warning = (
678 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
679 ' problem retrieving key binding for event %r\n'
680 ' from key set %r.\n'
681 ' returning default value: %r' %
682 (event, keySetName, keyBindings[event])
683 )
684 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000685 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000686
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400687 def GetExtraHelpSourceList(self, configSet):
688 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000689
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000690 Valid configSets are 'user' or 'default'. Return a list of tuples of
691 the form (menu_item , path_to_help_file , option), or return the empty
692 list. 'option' is the sequence number of the help resource. 'option'
693 values determine the position of the menu items on the Help menu,
694 therefore the returned list must be sorted by 'option'.
695
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000696 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400697 helpSources = []
698 if configSet == 'user':
699 cfgParser = self.userCfg['main']
700 elif configSet == 'default':
701 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000702 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000703 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000704 options=cfgParser.GetOptionList('HelpFiles')
705 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400706 value=cfgParser.Get('HelpFiles', option, default=';')
707 if value.find(';') == -1: #malformed config entry with no ';'
708 menuItem = '' #make these empty
709 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000710 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000711 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000712 menuItem=value[0].strip()
713 helpPath=value[1].strip()
714 if menuItem and helpPath: #neither are empty strings
715 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000716 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000717 return helpSources
718
719 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400720 """Return a list of the details of all additional help sources.
721
722 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000723 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400724 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000725 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000726 return allHelpSources
727
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400728 def GetFont(self, root, configType, section):
729 """Retrieve a font from configuration (font, font-size, font-bold)
730 Intercept the special value 'TkFixedFont' and substitute
731 the actual font, factoring in some tweaks if needed for
732 appearance sakes.
733
734 The 'root' parameter can normally be any valid Tkinter widget.
735
736 Return a tuple (family, size, weight) suitable for passing
737 to tkinter.Font
738 """
739 family = self.GetOption(configType, section, 'font', default='courier')
740 size = self.GetOption(configType, section, 'font-size', type='int',
741 default='10')
742 bold = self.GetOption(configType, section, 'font-bold', default=0,
743 type='bool')
744 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400745 f = Font(name='TkFixedFont', exists=True, root=root)
746 actualFont = Font.actual(f)
747 family = actualFont['family']
748 size = actualFont['size']
749 if size <= 0:
750 size = 10 # if font in pixels, ignore actual size
751 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400752 return (family, size, 'bold' if bold else 'normal')
753
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000754 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400755 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000756 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000757 self.defaultCfg[key].Load()
758 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000759
760 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400761 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000762 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000763 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000764
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000765
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400766idleConf = IdleConf()
767
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400768_warned = set()
769def _warn(msg, *key):
770 key = (msg,) + key
771 if key not in _warned:
772 try:
773 print(msg, file=sys.stderr)
774 except OSError:
775 pass
776 _warned.add(key)
777
778
terryjreedy349abd92017-07-07 16:00:57 -0400779class ConfigChanges(dict):
780 """Manage a user's proposed configuration option changes.
781
782 Names used across multiple methods:
783 page -- one of the 4 top-level dicts representing a
784 .idlerc/config-x.cfg file.
785 config_type -- name of a page.
786 section -- a section within a page/file.
787 option -- name of an option within a section.
788 value -- value for the option.
789
790 Methods
791 add_option: Add option and value to changes.
792 save_option: Save option and value to config parser.
793 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400794 delete_section: If section exists,
795 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400796 clear: Clear all changes by clearing each page.
797 """
798 def __init__(self):
799 "Create a page for each configuration file"
800 self.pages = [] # List of unhashable dicts.
801 for config_type in idleConf.config_types:
802 self[config_type] = {}
803 self.pages.append(self[config_type])
804
805 def add_option(self, config_type, section, item, value):
806 "Add item/value pair for config_type and section."
807 page = self[config_type]
808 value = str(value) # Make sure we use a string.
809 if section not in page:
810 page[section] = {}
811 page[section][item] = value
812
813 @staticmethod
814 def save_option(config_type, section, item, value):
815 """Return True if the configuration value was added or changed.
816
817 Helper for save_all.
818 """
819 if idleConf.defaultCfg[config_type].has_option(section, item):
820 if idleConf.defaultCfg[config_type].Get(section, item) == value:
821 # The setting equals a default setting, remove it from user cfg.
822 return idleConf.userCfg[config_type].RemoveOption(section, item)
823 # If we got here, set the option.
824 return idleConf.userCfg[config_type].SetOption(section, item, value)
825
826 def save_all(self):
827 """Save configuration changes to the user config file.
828
Louie Lu50c94352017-07-13 02:05:32 +0800829 Clear self in preparation for additional changes.
830 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400831 """
832 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800833
834 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400835 for config_type in self:
836 cfg_type_changed = False
837 page = self[config_type]
838 for section in page:
839 if section == 'HelpFiles': # Remove it for replacement.
840 idleConf.userCfg['main'].remove_section('HelpFiles')
841 cfg_type_changed = True
842 for item, value in page[section].items():
843 if self.save_option(config_type, section, item, value):
844 cfg_type_changed = True
845 if cfg_type_changed:
846 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800847 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400848 for config_type in ['keys', 'highlight']:
849 # Save these even if unchanged!
850 idleConf.userCfg[config_type].Save()
851 self.clear()
852 # ConfigDialog caller must add the following call
853 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800854 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400855
856 def delete_section(self, config_type, section):
857 """Delete a section from self, userCfg, and file.
858
859 Used to delete custom themes and keysets.
860 """
861 if section in self[config_type]:
862 del self[config_type][section]
863 configpage = idleConf.userCfg[config_type]
864 configpage.remove_section(section)
865 configpage.Save()
866
867 def clear(self):
868 """Clear all 4 pages.
869
870 Called in save_all after saving to idleConf.
871 XXX Mark window *title* when there are changes; unmark here.
872 """
873 for page in self.pages:
874 page.clear()
875
876
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400877# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400878def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400879 from zlib import crc32
880 line, crc = 0, 0
881
882 def sprint(obj):
883 global line, crc
884 txt = str(obj)
885 line += 1
886 crc = crc32(txt.encode(encoding='utf-8'), crc)
887 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400888 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400889
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000890 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400891 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400892 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400893 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400894 sprint(key)
895 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000896 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400897 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400898 sprint(section)
899 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000900 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400901 sprint(option + ' = ' + cfg[key].Get(section, option))
902
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000903 dumpCfg(idleConf.defaultCfg)
904 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400905 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400906
907if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400908 from unittest import main
909 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
910
911 # Run revised _dump() as htest?