blob: 0c55c9a7d75d8df3a4c6b8adf466640c5d222254 [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',
322 #cursor (only foreground can be set)
323 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000324 #shell window
325 'stdout-foreground':'#000000',
326 'stdout-background':'#ffffff',
327 'stderr-foreground':'#000000',
328 'stderr-background':'#ffffff',
329 'console-foreground':'#000000',
wohlganger58fc71c2017-09-10 16:19:47 -0500330 'console-background':'#ffffff',
Cheryl Sabellade651622018-06-01 21:45:54 -0400331 'context-foreground':'#000000',
332 'context-background':'#ffffff',
wohlganger58fc71c2017-09-10 16:19:47 -0500333 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000334 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400335 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400336 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400337 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000338 ' -\n problem retrieving theme element %r'
339 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400340 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000341 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400342 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400343 theme[element] = cfgParser.Get(
344 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000345 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000346
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000347 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400348 "Return the name of the currently active text color theme."
349 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500350
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400351 def CurrentKeys(self):
352 """Return the name of the currently active key set."""
353 return self.current_colors_and_keys('Keys')
354
355 def current_colors_and_keys(self, section):
356 """Return the currently active name for Theme or Keys section.
357
358 idlelib.config-main.def ('default') includes these sections
359
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500360 [Theme]
361 default= 1
362 name= IDLE Classic
363 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500364
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400365 [Keys]
366 default= 1
367 name=
368 name2=
369
370 Item 'name2', is used for built-in ('default') themes and keys
371 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
372 because setting 'name' to a builtin not defined in older IDLEs
373 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500374 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400375 When default = True, 'name2' takes precedence over 'name',
376 while older IDLEs will just use name. When default = False,
377 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500378 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400379 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400380 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500381 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400382 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500383 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400384 name = self.GetOption('main', section, 'name2', default='')
385 if not name:
386 name = self.GetOption('main', section, 'name', default='')
387 if name:
388 source = self.defaultCfg if default else self.userCfg
389 if source[cfgname].has_section(name):
390 return name
391 return "IDLE Classic" if section == 'Theme' else self.default_keys()
392
393 @staticmethod
394 def default_keys():
395 if sys.platform[:3] == 'win':
396 return 'IDLE Classic Windows'
397 elif sys.platform == 'darwin':
398 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500399 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400400 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000401
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400402 def GetExtensions(self, active_only=True,
403 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400404 """Return extensions in default and user config-extensions files.
405
406 If active_only True, only return active (enabled) extensions
407 and optionally only editor or shell extensions.
408 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000409 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400410 extns = self.RemoveKeyBindNames(
411 self.GetSectionList('default', 'extensions'))
412 userExtns = self.RemoveKeyBindNames(
413 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000414 for extn in userExtns:
415 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000416 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500417 for extn in ('AutoComplete','CodeContext',
418 'FormatParagraph','ParenMatch'):
419 extns.remove(extn)
420 # specific exclusions because we are storing config for mainlined old
421 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000422 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400423 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000424 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000425 if self.GetOption('extensions', extn, 'enable', default=True,
426 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000427 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400428 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000429 if editor_only:
430 option = "enable_editor"
431 else:
432 option = "enable_shell"
433 if self.GetOption('extensions', extn,option,
434 default=True, type='bool',
435 warn_on_default=False):
436 activeExtns.append(extn)
437 else:
438 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000439 return activeExtns
440 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000441 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000442
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400443 def RemoveKeyBindNames(self, extnNameList):
444 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800445 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000446
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400447 def GetExtnNameForEvent(self, virtualEvent):
448 """Return the name of the extension binding virtualEvent, or None.
449
450 virtualEvent - string, name of the virtual event to test for,
451 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000452 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400453 extName = None
454 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000455 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000456 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000457 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400458 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000459 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000460
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400461 def GetExtensionKeys(self, extensionName):
462 """Return dict: {configurable extensionName event : active keybinding}.
463
464 Events come from default config extension_cfgBindings section.
465 Keybindings come from GetCurrentKeySet() active key dict,
466 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000467 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400468 keysName = extensionName + '_cfgBindings'
469 activeKeys = self.GetCurrentKeySet()
470 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000471 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400472 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000473 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400474 event = '<<' + eventName + '>>'
475 binding = activeKeys[event]
476 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000477 return extKeys
478
Steven M. Gavac628a062002-01-19 10:33:21 +0000479 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400480 """Return dict {configurable extensionName event : keybinding list}.
481
482 Events come from default config extension_cfgBindings section.
483 Keybindings list come from the splitting of GetOption, which
484 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000485 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400486 keysName = extensionName+'_cfgBindings'
487 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000488 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400489 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000490 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400491 binding = self.GetOption(
492 'extensions', keysName, eventName, default='').split()
493 event = '<<' + eventName + '>>'
494 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000495 return extKeys
496
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400497 def GetExtensionBindings(self, extensionName):
498 """Return dict {extensionName event : active or defined keybinding}.
499
500 Augment self.GetExtensionKeys(extensionName) with mapping of non-
501 configurable events (from default config) to GetOption splits,
502 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000503 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400504 bindsName = extensionName + '_bindings'
505 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000506 #add the non-configurable bindings
507 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400508 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000509 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400510 binding = self.GetOption(
511 'extensions', bindsName, eventName, default='').split()
512 event = '<<' + eventName + '>>'
513 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000514
515 return extBinds
516
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000517 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400518 """Return the keybinding list for keySetName eventStr.
519
520 keySetName - name of key binding set (config-keys section).
521 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000522 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400523 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400524 binding = self.GetOption('keys', keySetName, eventName, default='',
525 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000526 return binding
527
Steven M. Gavac628a062002-01-19 10:33:21 +0000528 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400529 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000530 result = self.GetKeySet(self.CurrentKeys())
531
Ned Deilyb7601672014-03-27 20:49:14 -0700532 if sys.platform == "darwin":
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500533 # macOS (OS X) Tk variants do not support the "Alt"
534 # keyboard modifier. Replace it with "Option".
535 # TODO (Ned?): the "Option" modifier does not work properly
536 # for Cocoa Tk and XQuartz Tk so we should not use it
537 # in the default 'OSX' keyset.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000538 for k, v in result.items():
539 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
540 if v != v2:
541 result[k] = v2
542
543 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000544
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400545 def GetKeySet(self, keySetName):
546 """Return event-key dict for keySetName core plus active extensions.
547
548 If a binding defined in an extension is already in use, the
549 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000550 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400551 keySet = self.GetCoreKeys(keySetName)
552 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000553 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400554 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000555 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000556 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000557 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000558 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400559 extKeys[event] = '' #disable this binding
560 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000561 return keySet
562
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400563 def IsCoreBinding(self, virtualEvent):
564 """Return True if the virtual event is one of the core idle key events.
565
566 virtualEvent - string, name of the virtual event to test for,
567 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000568 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000569 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000570
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400571# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500572# and copied in function below.
573
574 former_extension_events = { # Those with user-configurable keys.
575 '<<force-open-completions>>', '<<expand-word>>',
576 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400577 '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
578 '<<run-custom>>',
579 }
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400580
Steven M. Gavac628a062002-01-19 10:33:21 +0000581 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400582 """Return dict of core virtual-key keybindings for keySetName.
583
584 The default keySetName None corresponds to the keyBindings base
585 dict. If keySetName is not None, bindings from the config
586 file(s) are loaded _over_ these defaults, so if there is a
587 problem getting any core binding there will be an 'ultimate last
588 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000589 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000590 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000591 '<<copy>>': ['<Control-c>', '<Control-C>'],
592 '<<cut>>': ['<Control-x>', '<Control-X>'],
593 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000594 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
595 '<<center-insert>>': ['<Control-l>'],
596 '<<close-all-windows>>': ['<Control-q>'],
597 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000598 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000599 '<<end-of-file>>': ['<Control-d>'],
600 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000601 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000602 '<<history-next>>': ['<Alt-n>'],
603 '<<history-previous>>': ['<Alt-p>'],
604 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000605 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000606 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000607 '<<open-class-browser>>': ['<Alt-c>'],
608 '<<open-module>>': ['<Alt-m>'],
609 '<<open-new-window>>': ['<Control-n>'],
610 '<<open-window-from-file>>': ['<Control-o>'],
611 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000612 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000613 '<<redo>>': ['<Control-y>'],
614 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000615 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000616 '<<save-window-as-file>>': ['<Alt-s>'],
617 '<<save-window>>': ['<Control-s>'],
618 '<<select-all>>': ['<Alt-a>'],
619 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000620 '<<undo>>': ['<Control-z>'],
621 '<<find-again>>': ['<Control-g>', '<F3>'],
622 '<<find-in-files>>': ['<Alt-F3>'],
623 '<<find-selection>>': ['<Control-F3>'],
624 '<<find>>': ['<Control-f>'],
625 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000626 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000627 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300628 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000629 '<<smart-indent>>': ['<Key-Tab>'],
630 '<<indent-region>>': ['<Control-Key-bracketright>'],
631 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
632 '<<comment-region>>': ['<Alt-Key-3>'],
633 '<<uncomment-region>>': ['<Alt-Key-4>'],
634 '<<tabify-region>>': ['<Alt-Key-5>'],
635 '<<untabify-region>>': ['<Alt-Key-6>'],
636 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000637 '<<change-indentwidth>>': ['<Alt-Key-u>'],
638 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500639 '<<del-word-right>>': ['<Control-Key-Delete>'],
640 '<<force-open-completions>>': ['<Control-Key-space>'],
641 '<<expand-word>>': ['<Alt-Key-slash>'],
642 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
643 '<<flash-paren>>': ['<Control-Key-0>'],
644 '<<format-paragraph>>': ['<Alt-Key-q>'],
645 '<<run-module>>': ['<Key-F5>'],
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400646 '<<run-custom>>': ['<Shift-Key-F5>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500647 '<<check-module>>': ['<Alt-Key-x>'],
648 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000649 }
wohlganger58fc71c2017-09-10 16:19:47 -0500650
Steven M. Gava17d01542001-12-03 00:37:28 +0000651 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400652 if not (self.userCfg['keys'].has_section(keySetName) or
653 self.defaultCfg['keys'].has_section(keySetName)):
654 warning = (
655 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
656 ' key set %r is not defined, using default bindings.' %
657 (keySetName,)
658 )
659 _warn(warning, 'keys', keySetName)
660 else:
661 for event in keyBindings:
662 binding = self.GetKeyBinding(keySetName, event)
663 if binding:
664 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500665 # Otherwise return default in keyBindings.
666 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400667 warning = (
668 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
669 ' problem retrieving key binding for event %r\n'
670 ' from key set %r.\n'
671 ' returning default value: %r' %
672 (event, keySetName, keyBindings[event])
673 )
674 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000675 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000676
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400677 def GetExtraHelpSourceList(self, configSet):
678 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000679
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000680 Valid configSets are 'user' or 'default'. Return a list of tuples of
681 the form (menu_item , path_to_help_file , option), or return the empty
682 list. 'option' is the sequence number of the help resource. 'option'
683 values determine the position of the menu items on the Help menu,
684 therefore the returned list must be sorted by 'option'.
685
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000686 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400687 helpSources = []
688 if configSet == 'user':
689 cfgParser = self.userCfg['main']
690 elif configSet == 'default':
691 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000692 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000693 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000694 options=cfgParser.GetOptionList('HelpFiles')
695 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400696 value=cfgParser.Get('HelpFiles', option, default=';')
697 if value.find(';') == -1: #malformed config entry with no ';'
698 menuItem = '' #make these empty
699 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000700 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000701 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000702 menuItem=value[0].strip()
703 helpPath=value[1].strip()
704 if menuItem and helpPath: #neither are empty strings
705 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000706 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000707 return helpSources
708
709 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400710 """Return a list of the details of all additional help sources.
711
712 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000713 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400714 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000715 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000716 return allHelpSources
717
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400718 def GetFont(self, root, configType, section):
719 """Retrieve a font from configuration (font, font-size, font-bold)
720 Intercept the special value 'TkFixedFont' and substitute
721 the actual font, factoring in some tweaks if needed for
722 appearance sakes.
723
724 The 'root' parameter can normally be any valid Tkinter widget.
725
726 Return a tuple (family, size, weight) suitable for passing
727 to tkinter.Font
728 """
729 family = self.GetOption(configType, section, 'font', default='courier')
730 size = self.GetOption(configType, section, 'font-size', type='int',
731 default='10')
732 bold = self.GetOption(configType, section, 'font-bold', default=0,
733 type='bool')
734 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400735 f = Font(name='TkFixedFont', exists=True, root=root)
736 actualFont = Font.actual(f)
737 family = actualFont['family']
738 size = actualFont['size']
739 if size <= 0:
740 size = 10 # if font in pixels, ignore actual size
741 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400742 return (family, size, 'bold' if bold else 'normal')
743
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000744 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400745 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000746 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000747 self.defaultCfg[key].Load()
748 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000749
750 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400751 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000752 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000753 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000754
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000755
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400756idleConf = IdleConf()
757
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400758_warned = set()
759def _warn(msg, *key):
760 key = (msg,) + key
761 if key not in _warned:
762 try:
763 print(msg, file=sys.stderr)
764 except OSError:
765 pass
766 _warned.add(key)
767
768
terryjreedy349abd92017-07-07 16:00:57 -0400769class ConfigChanges(dict):
770 """Manage a user's proposed configuration option changes.
771
772 Names used across multiple methods:
773 page -- one of the 4 top-level dicts representing a
774 .idlerc/config-x.cfg file.
775 config_type -- name of a page.
776 section -- a section within a page/file.
777 option -- name of an option within a section.
778 value -- value for the option.
779
780 Methods
781 add_option: Add option and value to changes.
782 save_option: Save option and value to config parser.
783 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400784 delete_section: If section exists,
785 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400786 clear: Clear all changes by clearing each page.
787 """
788 def __init__(self):
789 "Create a page for each configuration file"
790 self.pages = [] # List of unhashable dicts.
791 for config_type in idleConf.config_types:
792 self[config_type] = {}
793 self.pages.append(self[config_type])
794
795 def add_option(self, config_type, section, item, value):
796 "Add item/value pair for config_type and section."
797 page = self[config_type]
798 value = str(value) # Make sure we use a string.
799 if section not in page:
800 page[section] = {}
801 page[section][item] = value
802
803 @staticmethod
804 def save_option(config_type, section, item, value):
805 """Return True if the configuration value was added or changed.
806
807 Helper for save_all.
808 """
809 if idleConf.defaultCfg[config_type].has_option(section, item):
810 if idleConf.defaultCfg[config_type].Get(section, item) == value:
811 # The setting equals a default setting, remove it from user cfg.
812 return idleConf.userCfg[config_type].RemoveOption(section, item)
813 # If we got here, set the option.
814 return idleConf.userCfg[config_type].SetOption(section, item, value)
815
816 def save_all(self):
817 """Save configuration changes to the user config file.
818
Louie Lu50c94352017-07-13 02:05:32 +0800819 Clear self in preparation for additional changes.
820 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400821 """
822 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800823
824 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400825 for config_type in self:
826 cfg_type_changed = False
827 page = self[config_type]
828 for section in page:
829 if section == 'HelpFiles': # Remove it for replacement.
830 idleConf.userCfg['main'].remove_section('HelpFiles')
831 cfg_type_changed = True
832 for item, value in page[section].items():
833 if self.save_option(config_type, section, item, value):
834 cfg_type_changed = True
835 if cfg_type_changed:
836 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800837 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400838 for config_type in ['keys', 'highlight']:
839 # Save these even if unchanged!
840 idleConf.userCfg[config_type].Save()
841 self.clear()
842 # ConfigDialog caller must add the following call
843 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800844 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400845
846 def delete_section(self, config_type, section):
847 """Delete a section from self, userCfg, and file.
848
849 Used to delete custom themes and keysets.
850 """
851 if section in self[config_type]:
852 del self[config_type][section]
853 configpage = idleConf.userCfg[config_type]
854 configpage.remove_section(section)
855 configpage.Save()
856
857 def clear(self):
858 """Clear all 4 pages.
859
860 Called in save_all after saving to idleConf.
861 XXX Mark window *title* when there are changes; unmark here.
862 """
863 for page in self.pages:
864 page.clear()
865
866
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400867# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400868def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400869 from zlib import crc32
870 line, crc = 0, 0
871
872 def sprint(obj):
873 global line, crc
874 txt = str(obj)
875 line += 1
876 crc = crc32(txt.encode(encoding='utf-8'), crc)
877 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400878 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400879
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000880 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400881 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400882 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400883 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400884 sprint(key)
885 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000886 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400887 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400888 sprint(section)
889 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000890 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400891 sprint(option + ' = ' + cfg[key].Get(section, option))
892
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000893 dumpCfg(idleConf.defaultCfg)
894 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400895 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400896
897if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400898 from unittest import main
899 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
900
901 # Run revised _dump() as htest?