blob: 683b000a488b21c93263d15b75e2e9a405e89199 [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
133 if fname:
134 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
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400161
Louie Luf776eb02017-07-19 05:17:56 +0800162 if not _utest:
163 self.CreateConfigHandlers()
164 self.LoadCfgFiles()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000165
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000166 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400167 "Populate default and user config parser dictionaries."
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400168 idledir = os.path.dirname(__file__)
169 self.userdir = userdir = self.GetUserCfgDir()
170 for cfg_type in self.config_types:
171 self.defaultCfg[cfg_type] = IdleConfParser(
172 os.path.join(idledir, f'config-{cfg_type}.def'))
173 self.userCfg[cfg_type] = IdleUserConfParser(
174 os.path.join(userdir, f'config-{cfg_type}.cfg'))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000175
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000176 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400177 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000178
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400179 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000180 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000181 cfgDir = '.idlerc'
182 userDir = os.path.expanduser('~')
183 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000184 if not os.path.exists(userDir):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400185 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
186 userDir + ',\n but the path does not exist.')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000187 try:
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400188 print(warn, file=sys.stderr)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200189 except OSError:
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000190 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000191 userDir = '~'
192 if userDir == "~": # still no path to home!
193 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
194 userDir = os.getcwd()
195 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000196 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000197 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000198 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200199 except OSError:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400200 warn = ('\n Warning: unable to create user config directory\n' +
201 userDir + '\n Check path and permissions.\n Exiting!\n')
Louie Luf776eb02017-07-19 05:17:56 +0800202 if not idlelib.testing:
203 print(warn, file=sys.stderr)
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000204 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400205 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000206 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000207
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000208 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000209 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400210 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000211
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400212 If type is not None, return a value of that type. Also pass raw
213 to the config parser. First try to return a valid value
214 (including type) from a user configuration. If that fails, try
215 the default configuration. If that fails, return default, with a
216 default of None.
217
218 Warn if either user or default configurations have an invalid value.
219 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000220 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200221 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400222 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200223 return self.userCfg[configType].Get(section, option,
224 type=type, raw=raw)
225 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400226 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200227 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400228 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200229 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400230 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400231 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200232 try:
233 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400234 return self.defaultCfg[configType].Get(
235 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200236 except ValueError:
237 pass
238 #returning default, print warning
239 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400240 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200241 ' problem retrieving configuration option %r\n'
242 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400243 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200244 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400245 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200246 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000247
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000248 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400249 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000250 self.userCfg[configType].SetOption(section, option, value)
251
Steven M. Gava2a63a072001-10-26 06:50:54 +0000252 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400253 """Return sections for configSet configType configuration.
254
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000255 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400256 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000257 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400258 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000259 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000260 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400261 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000262 elif configSet == 'default':
263 cfgParser=self.defaultCfg[configType]
264 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000265 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000266 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000267
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400268 def GetHighlight(self, theme, element):
269 """Return dict of theme element highlight colors.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400270
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400271 The keys are 'foreground' and 'background'. The values are
272 tkinter color strings for configuring backgrounds and tags.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000273 """
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400274 cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
275 else 'user')
276 theme_dict = self.GetThemeDict(cfg, theme)
277 fore = theme_dict[element + '-foreground']
278 if element == 'cursor':
279 element = 'normal'
280 back = theme_dict[element + '-background']
281 return {"foreground": fore, "background": back}
Steven M. Gava9f25e672002-02-11 02:51:18 +0000282
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400283 def GetThemeDict(self, type, themeName):
284 """Return {option:value} dict for elements in themeName.
285
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000286 type - string, 'default' or 'user' theme type
287 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400288 Values are loaded over ultimate fallback defaults to guarantee
289 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000290 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000291 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400292 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000293 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400294 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000295 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000296 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400297 # Provide foreground and background colors for each theme
298 # element (other than cursor) even though some values are not
299 # yet used by idle, to allow for their use in the future.
300 # Default values are generally black and white.
301 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400302 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000303 'normal-background':'#ffffff',
304 'keyword-foreground':'#000000',
305 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000306 'builtin-foreground':'#000000',
307 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000308 'comment-foreground':'#000000',
309 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000310 'string-foreground':'#000000',
311 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000312 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000313 'definition-background':'#ffffff',
314 'hilite-foreground':'#000000',
315 'hilite-background':'gray',
316 'break-foreground':'#ffffff',
317 'break-background':'#000000',
318 'hit-foreground':'#ffffff',
319 'hit-background':'#000000',
320 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000321 'error-background':'#000000',
Tal Einat7123ea02019-07-23 15:22:11 +0300322 'context-foreground':'#000000',
323 'context-background':'#ffffff',
324 'linenumber-foreground':'#000000',
325 'linenumber-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000326 #cursor (only foreground can be set)
327 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000328 #shell window
329 'stdout-foreground':'#000000',
330 'stdout-background':'#ffffff',
331 'stderr-foreground':'#000000',
332 'stderr-background':'#ffffff',
333 'console-foreground':'#000000',
wohlganger58fc71c2017-09-10 16:19:47 -0500334 'console-background':'#ffffff',
335 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000336 for element in theme:
Tal Einat7123ea02019-07-23 15:22:11 +0300337 if not (cfgParser.has_option(themeName, element) or
338 # Skip warning for new elements.
339 element.startswith(('context-', 'linenumber-'))):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400340 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400341 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000342 ' -\n problem retrieving theme element %r'
343 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400344 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000345 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400346 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400347 theme[element] = cfgParser.Get(
348 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000349 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000350
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000351 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400352 "Return the name of the currently active text color theme."
353 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500354
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400355 def CurrentKeys(self):
356 """Return the name of the currently active key set."""
357 return self.current_colors_and_keys('Keys')
358
359 def current_colors_and_keys(self, section):
360 """Return the currently active name for Theme or Keys section.
361
362 idlelib.config-main.def ('default') includes these sections
363
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500364 [Theme]
365 default= 1
366 name= IDLE Classic
367 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500368
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400369 [Keys]
370 default= 1
371 name=
372 name2=
373
374 Item 'name2', is used for built-in ('default') themes and keys
375 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
376 because setting 'name' to a builtin not defined in older IDLEs
377 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500378 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400379 When default = True, 'name2' takes precedence over 'name',
380 while older IDLEs will just use name. When default = False,
381 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500382 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400383 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400384 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500385 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400386 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500387 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400388 name = self.GetOption('main', section, 'name2', default='')
389 if not name:
390 name = self.GetOption('main', section, 'name', default='')
391 if name:
392 source = self.defaultCfg if default else self.userCfg
393 if source[cfgname].has_section(name):
394 return name
395 return "IDLE Classic" if section == 'Theme' else self.default_keys()
396
397 @staticmethod
398 def default_keys():
399 if sys.platform[:3] == 'win':
400 return 'IDLE Classic Windows'
401 elif sys.platform == 'darwin':
402 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500403 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400404 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000405
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400406 def GetExtensions(self, active_only=True,
407 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400408 """Return extensions in default and user config-extensions files.
409
410 If active_only True, only return active (enabled) extensions
411 and optionally only editor or shell extensions.
412 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000413 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400414 extns = self.RemoveKeyBindNames(
415 self.GetSectionList('default', 'extensions'))
416 userExtns = self.RemoveKeyBindNames(
417 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000418 for extn in userExtns:
419 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000420 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500421 for extn in ('AutoComplete','CodeContext',
422 'FormatParagraph','ParenMatch'):
423 extns.remove(extn)
424 # specific exclusions because we are storing config for mainlined old
425 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000426 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400427 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000428 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000429 if self.GetOption('extensions', extn, 'enable', default=True,
430 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000431 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400432 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000433 if editor_only:
434 option = "enable_editor"
435 else:
436 option = "enable_shell"
437 if self.GetOption('extensions', extn,option,
438 default=True, type='bool',
439 warn_on_default=False):
440 activeExtns.append(extn)
441 else:
442 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000443 return activeExtns
444 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000445 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000446
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400447 def RemoveKeyBindNames(self, extnNameList):
448 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800449 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000450
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400451 def GetExtnNameForEvent(self, virtualEvent):
452 """Return the name of the extension binding virtualEvent, or None.
453
454 virtualEvent - string, name of the virtual event to test for,
455 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000456 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400457 extName = None
458 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000459 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000460 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000461 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400462 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000463 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000464
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400465 def GetExtensionKeys(self, extensionName):
466 """Return dict: {configurable extensionName event : active keybinding}.
467
468 Events come from default config extension_cfgBindings section.
469 Keybindings come from GetCurrentKeySet() active key dict,
470 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000471 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400472 keysName = extensionName + '_cfgBindings'
473 activeKeys = self.GetCurrentKeySet()
474 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000475 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400476 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000477 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400478 event = '<<' + eventName + '>>'
479 binding = activeKeys[event]
480 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000481 return extKeys
482
Steven M. Gavac628a062002-01-19 10:33:21 +0000483 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400484 """Return dict {configurable extensionName event : keybinding list}.
485
486 Events come from default config extension_cfgBindings section.
487 Keybindings list come from the splitting of GetOption, which
488 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000489 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400490 keysName = extensionName+'_cfgBindings'
491 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000492 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400493 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000494 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400495 binding = self.GetOption(
496 'extensions', keysName, eventName, default='').split()
497 event = '<<' + eventName + '>>'
498 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000499 return extKeys
500
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400501 def GetExtensionBindings(self, extensionName):
502 """Return dict {extensionName event : active or defined keybinding}.
503
504 Augment self.GetExtensionKeys(extensionName) with mapping of non-
505 configurable events (from default config) to GetOption splits,
506 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000507 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400508 bindsName = extensionName + '_bindings'
509 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000510 #add the non-configurable bindings
511 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400512 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000513 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400514 binding = self.GetOption(
515 'extensions', bindsName, eventName, default='').split()
516 event = '<<' + eventName + '>>'
517 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000518
519 return extBinds
520
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000521 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400522 """Return the keybinding list for keySetName eventStr.
523
524 keySetName - name of key binding set (config-keys section).
525 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000526 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400527 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400528 binding = self.GetOption('keys', keySetName, eventName, default='',
529 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000530 return binding
531
Steven M. Gavac628a062002-01-19 10:33:21 +0000532 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400533 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000534 result = self.GetKeySet(self.CurrentKeys())
535
Ned Deilyb7601672014-03-27 20:49:14 -0700536 if sys.platform == "darwin":
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500537 # macOS (OS X) Tk variants do not support the "Alt"
538 # keyboard modifier. Replace it with "Option".
539 # TODO (Ned?): the "Option" modifier does not work properly
540 # for Cocoa Tk and XQuartz Tk so we should not use it
541 # in the default 'OSX' keyset.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000542 for k, v in result.items():
543 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
544 if v != v2:
545 result[k] = v2
546
547 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000548
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400549 def GetKeySet(self, keySetName):
550 """Return event-key dict for keySetName core plus active extensions.
551
552 If a binding defined in an extension is already in use, the
553 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000554 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400555 keySet = self.GetCoreKeys(keySetName)
556 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000557 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400558 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000559 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000560 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000561 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000562 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400563 extKeys[event] = '' #disable this binding
564 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000565 return keySet
566
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400567 def IsCoreBinding(self, virtualEvent):
568 """Return True if the virtual event is one of the core idle key events.
569
570 virtualEvent - string, name of the virtual event to test for,
571 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000572 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000573 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000574
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400575# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500576# and copied in function below.
577
578 former_extension_events = { # Those with user-configurable keys.
579 '<<force-open-completions>>', '<<expand-word>>',
580 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400581 '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
582 '<<run-custom>>',
583 }
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400584
Steven M. Gavac628a062002-01-19 10:33:21 +0000585 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400586 """Return dict of core virtual-key keybindings for keySetName.
587
588 The default keySetName None corresponds to the keyBindings base
589 dict. If keySetName is not None, bindings from the config
590 file(s) are loaded _over_ these defaults, so if there is a
591 problem getting any core binding there will be an 'ultimate last
592 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000593 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000594 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000595 '<<copy>>': ['<Control-c>', '<Control-C>'],
596 '<<cut>>': ['<Control-x>', '<Control-X>'],
597 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000598 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
599 '<<center-insert>>': ['<Control-l>'],
600 '<<close-all-windows>>': ['<Control-q>'],
601 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000602 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000603 '<<end-of-file>>': ['<Control-d>'],
604 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000605 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000606 '<<history-next>>': ['<Alt-n>'],
607 '<<history-previous>>': ['<Alt-p>'],
608 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000609 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000610 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000611 '<<open-class-browser>>': ['<Alt-c>'],
612 '<<open-module>>': ['<Alt-m>'],
613 '<<open-new-window>>': ['<Control-n>'],
614 '<<open-window-from-file>>': ['<Control-o>'],
615 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000616 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000617 '<<redo>>': ['<Control-y>'],
618 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000619 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000620 '<<save-window-as-file>>': ['<Alt-s>'],
621 '<<save-window>>': ['<Control-s>'],
622 '<<select-all>>': ['<Alt-a>'],
623 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000624 '<<undo>>': ['<Control-z>'],
625 '<<find-again>>': ['<Control-g>', '<F3>'],
626 '<<find-in-files>>': ['<Alt-F3>'],
627 '<<find-selection>>': ['<Control-F3>'],
628 '<<find>>': ['<Control-f>'],
629 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000630 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000631 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300632 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000633 '<<smart-indent>>': ['<Key-Tab>'],
634 '<<indent-region>>': ['<Control-Key-bracketright>'],
635 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
636 '<<comment-region>>': ['<Alt-Key-3>'],
637 '<<uncomment-region>>': ['<Alt-Key-4>'],
638 '<<tabify-region>>': ['<Alt-Key-5>'],
639 '<<untabify-region>>': ['<Alt-Key-6>'],
640 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000641 '<<change-indentwidth>>': ['<Alt-Key-u>'],
642 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500643 '<<del-word-right>>': ['<Control-Key-Delete>'],
644 '<<force-open-completions>>': ['<Control-Key-space>'],
645 '<<expand-word>>': ['<Alt-Key-slash>'],
646 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
647 '<<flash-paren>>': ['<Control-Key-0>'],
648 '<<format-paragraph>>': ['<Alt-Key-q>'],
649 '<<run-module>>': ['<Key-F5>'],
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400650 '<<run-custom>>': ['<Shift-Key-F5>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500651 '<<check-module>>': ['<Alt-Key-x>'],
652 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000653 }
wohlganger58fc71c2017-09-10 16:19:47 -0500654
Steven M. Gava17d01542001-12-03 00:37:28 +0000655 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400656 if not (self.userCfg['keys'].has_section(keySetName) or
657 self.defaultCfg['keys'].has_section(keySetName)):
658 warning = (
659 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
660 ' key set %r is not defined, using default bindings.' %
661 (keySetName,)
662 )
663 _warn(warning, 'keys', keySetName)
664 else:
665 for event in keyBindings:
666 binding = self.GetKeyBinding(keySetName, event)
667 if binding:
668 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500669 # Otherwise return default in keyBindings.
670 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400671 warning = (
672 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
673 ' problem retrieving key binding for event %r\n'
674 ' from key set %r.\n'
675 ' returning default value: %r' %
676 (event, keySetName, keyBindings[event])
677 )
678 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000679 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000680
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400681 def GetExtraHelpSourceList(self, configSet):
682 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000683
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000684 Valid configSets are 'user' or 'default'. Return a list of tuples of
685 the form (menu_item , path_to_help_file , option), or return the empty
686 list. 'option' is the sequence number of the help resource. 'option'
687 values determine the position of the menu items on the Help menu,
688 therefore the returned list must be sorted by 'option'.
689
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000690 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400691 helpSources = []
692 if configSet == 'user':
693 cfgParser = self.userCfg['main']
694 elif configSet == 'default':
695 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000696 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000697 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000698 options=cfgParser.GetOptionList('HelpFiles')
699 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400700 value=cfgParser.Get('HelpFiles', option, default=';')
701 if value.find(';') == -1: #malformed config entry with no ';'
702 menuItem = '' #make these empty
703 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000704 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000705 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000706 menuItem=value[0].strip()
707 helpPath=value[1].strip()
708 if menuItem and helpPath: #neither are empty strings
709 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000710 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000711 return helpSources
712
713 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400714 """Return a list of the details of all additional help sources.
715
716 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000717 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400718 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000719 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000720 return allHelpSources
721
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400722 def GetFont(self, root, configType, section):
723 """Retrieve a font from configuration (font, font-size, font-bold)
724 Intercept the special value 'TkFixedFont' and substitute
725 the actual font, factoring in some tweaks if needed for
726 appearance sakes.
727
728 The 'root' parameter can normally be any valid Tkinter widget.
729
730 Return a tuple (family, size, weight) suitable for passing
731 to tkinter.Font
732 """
733 family = self.GetOption(configType, section, 'font', default='courier')
734 size = self.GetOption(configType, section, 'font-size', type='int',
735 default='10')
736 bold = self.GetOption(configType, section, 'font-bold', default=0,
737 type='bool')
738 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400739 f = Font(name='TkFixedFont', exists=True, root=root)
740 actualFont = Font.actual(f)
741 family = actualFont['family']
742 size = actualFont['size']
743 if size <= 0:
744 size = 10 # if font in pixels, ignore actual size
745 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400746 return (family, size, 'bold' if bold else 'normal')
747
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000748 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400749 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000750 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000751 self.defaultCfg[key].Load()
752 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000753
754 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400755 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000756 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000757 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000758
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000759
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400760idleConf = IdleConf()
761
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400762_warned = set()
763def _warn(msg, *key):
764 key = (msg,) + key
765 if key not in _warned:
766 try:
767 print(msg, file=sys.stderr)
768 except OSError:
769 pass
770 _warned.add(key)
771
772
terryjreedy349abd92017-07-07 16:00:57 -0400773class ConfigChanges(dict):
774 """Manage a user's proposed configuration option changes.
775
776 Names used across multiple methods:
777 page -- one of the 4 top-level dicts representing a
778 .idlerc/config-x.cfg file.
779 config_type -- name of a page.
780 section -- a section within a page/file.
781 option -- name of an option within a section.
782 value -- value for the option.
783
784 Methods
785 add_option: Add option and value to changes.
786 save_option: Save option and value to config parser.
787 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400788 delete_section: If section exists,
789 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400790 clear: Clear all changes by clearing each page.
791 """
792 def __init__(self):
793 "Create a page for each configuration file"
794 self.pages = [] # List of unhashable dicts.
795 for config_type in idleConf.config_types:
796 self[config_type] = {}
797 self.pages.append(self[config_type])
798
799 def add_option(self, config_type, section, item, value):
800 "Add item/value pair for config_type and section."
801 page = self[config_type]
802 value = str(value) # Make sure we use a string.
803 if section not in page:
804 page[section] = {}
805 page[section][item] = value
806
807 @staticmethod
808 def save_option(config_type, section, item, value):
809 """Return True if the configuration value was added or changed.
810
811 Helper for save_all.
812 """
813 if idleConf.defaultCfg[config_type].has_option(section, item):
814 if idleConf.defaultCfg[config_type].Get(section, item) == value:
815 # The setting equals a default setting, remove it from user cfg.
816 return idleConf.userCfg[config_type].RemoveOption(section, item)
817 # If we got here, set the option.
818 return idleConf.userCfg[config_type].SetOption(section, item, value)
819
820 def save_all(self):
821 """Save configuration changes to the user config file.
822
Louie Lu50c94352017-07-13 02:05:32 +0800823 Clear self in preparation for additional changes.
824 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400825 """
826 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800827
828 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400829 for config_type in self:
830 cfg_type_changed = False
831 page = self[config_type]
832 for section in page:
833 if section == 'HelpFiles': # Remove it for replacement.
834 idleConf.userCfg['main'].remove_section('HelpFiles')
835 cfg_type_changed = True
836 for item, value in page[section].items():
837 if self.save_option(config_type, section, item, value):
838 cfg_type_changed = True
839 if cfg_type_changed:
840 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800841 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400842 for config_type in ['keys', 'highlight']:
843 # Save these even if unchanged!
844 idleConf.userCfg[config_type].Save()
845 self.clear()
846 # ConfigDialog caller must add the following call
847 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800848 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400849
850 def delete_section(self, config_type, section):
851 """Delete a section from self, userCfg, and file.
852
853 Used to delete custom themes and keysets.
854 """
855 if section in self[config_type]:
856 del self[config_type][section]
857 configpage = idleConf.userCfg[config_type]
858 configpage.remove_section(section)
859 configpage.Save()
860
861 def clear(self):
862 """Clear all 4 pages.
863
864 Called in save_all after saving to idleConf.
865 XXX Mark window *title* when there are changes; unmark here.
866 """
867 for page in self.pages:
868 page.clear()
869
870
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400871# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400872def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400873 from zlib import crc32
874 line, crc = 0, 0
875
876 def sprint(obj):
877 global line, crc
878 txt = str(obj)
879 line += 1
880 crc = crc32(txt.encode(encoding='utf-8'), crc)
881 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400882 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400883
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000884 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400885 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400886 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400887 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400888 sprint(key)
889 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000890 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400891 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400892 sprint(section)
893 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000894 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400895 sprint(option + ' = ' + cfg[key].Get(section, option))
896
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000897 dumpCfg(idleConf.defaultCfg)
898 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400899 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400900
901if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400902 from unittest import main
903 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
904
905 # Run revised _dump() as htest?