blob: 12e6f9fae78b80fdada01e1723e99c9a6c6c13f2 [file] [log] [blame]
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001"""idlelib.config -- Manage IDLE configuration information.
Steven M. Gavac5976402002-01-04 03:06:08 +00002
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05003The comments at the beginning of config-main.def describe the
4configuration files and the design implemented to update user
5configuration information. In particular, user configuration choices
6which duplicate the defaults will be removed from the user's
7configuration files, and if a user file becomes empty, it will be
8deleted.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00009
KunYuChenf3e82092017-06-21 12:30:45 +080010The configuration database maps options to values. Conceptually, the
Terry Jan Reedyf46b7822016-11-07 17:15:01 -050011database keys are tuples (config-type, section, item). As implemented,
12there are separate dicts for default and user values. Each has
13config-type keys 'main', 'extensions', 'highlight', and 'keys'. The
14value for each key is a ConfigParser instance that maps section and item
Xtreakd9677f32019-06-03 09:51:15 +053015to values. For 'main' and 'extensions', user values override
Terry Jan Reedyf46b7822016-11-07 17:15:01 -050016default values. For 'highlight' and 'keys', user sections augment the
17default sections (and must, therefore, have distinct names).
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000018
19Throughout this module there is an emphasis on returning useable defaults
20when a problem occurs in returning a requested configuration value back to
21idle. This is to allow IDLE to continue to function in spite of errors in
22the retrieval of config information. When a default is returned instead of
23a requested config value, a message is printed to stderr to aid in
24configuration problem notification and resolution.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000025"""
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040026# TODOs added Oct 2014, tjr
27
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040028from configparser import ConfigParser
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000029import os
30import sys
Guido van Rossum36e0a922007-07-20 04:05:57 +000031
Victor Stinnerd6debb22017-03-27 16:05:26 +020032from tkinter.font import Font
Louie Luf776eb02017-07-19 05:17:56 +080033import idlelib
Steven M. Gavac11ccf32001-09-24 09:43:17 +000034
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000035class InvalidConfigType(Exception): pass
36class InvalidConfigSet(Exception): pass
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000037class InvalidTheme(Exception): pass
38
Steven M. Gavac11ccf32001-09-24 09:43:17 +000039class IdleConfParser(ConfigParser):
40 """
41 A ConfigParser specialised for idle configuration file handling
42 """
43 def __init__(self, cfgFile, cfgDefaults=None):
44 """
45 cfgFile - string, fully specified configuration file name
46 """
terryjreedy349abd92017-07-07 16:00:57 -040047 self.file = cfgFile # This is currently '' when testing.
Serhiy Storchaka89953002013-02-07 15:24:36 +020048 ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000049
Thomas Wouterscf297e42007-02-23 15:07:44 +000050 def Get(self, section, option, type=None, default=None, raw=False):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000051 """
52 Get an option value for given section/option or return default.
53 If type is specified, return as type.
54 """
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040055 # TODO Use default as fallback, at least if not None
56 # Should also print Warning(file, section, option).
57 # Currently may raise ValueError
Thomas Wouterscf297e42007-02-23 15:07:44 +000058 if not self.has_option(section, option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000059 return default
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040060 if type == 'bool':
Thomas Wouterscf297e42007-02-23 15:07:44 +000061 return self.getboolean(section, option)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040062 elif type == 'int':
Thomas Wouterscf297e42007-02-23 15:07:44 +000063 return self.getint(section, option)
64 else:
65 return self.get(section, option, raw=raw)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000066
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040067 def GetOptionList(self, section):
68 "Return a list of options for given section, else []."
Steven M. Gava085eb1b2002-02-05 04:52:32 +000069 if self.has_section(section):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000070 return self.options(section)
71 else: #return a default value
72 return []
73
Steven M. Gavac11ccf32001-09-24 09:43:17 +000074 def Load(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040075 "Load the configuration file from disk."
terryjreedy349abd92017-07-07 16:00:57 -040076 if self.file:
77 self.read(self.file)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000078
Steven M. Gavac11ccf32001-09-24 09:43:17 +000079class IdleUserConfParser(IdleConfParser):
80 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000081 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000082 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000083
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040084 def SetOption(self, section, option, value):
85 """Return True if option is added or changed to value, else False.
86
87 Add section if required. False means option already had value.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000088 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040089 if self.has_option(section, option):
90 if self.get(section, option) == value:
91 return False
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000092 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040093 self.set(section, option, value)
94 return True
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000095 else:
96 if not self.has_section(section):
97 self.add_section(section)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040098 self.set(section, option, value)
99 return True
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000100
Louie Lu50c94352017-07-13 02:05:32 +0800101 def RemoveOption(self, section, option):
102 """Return True if option is removed from section, else False.
103
104 False if either section does not exist or did not have option.
105 """
106 if self.has_section(section):
107 return self.remove_option(section, option)
108 return False
109
110 def AddSection(self, section):
111 "If section doesn't exist, add it."
112 if not self.has_section(section):
113 self.add_section(section)
114
115 def RemoveEmptySections(self):
116 "Remove any sections that have no options."
117 for section in self.sections():
118 if not self.GetOptionList(section):
119 self.remove_section(section)
120
121 def IsEmpty(self):
122 "Return True if no sections after removing empty sections."
123 self.RemoveEmptySections()
124 return not self.sections()
125
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000126 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000127 """Update user configuration file.
128
terryjreedy349abd92017-07-07 16:00:57 -0400129 If self not empty after removing empty sections, write the file
130 to disk. Otherwise, remove the file from disk if it exists.
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000131 """
terryjreedy349abd92017-07-07 16:00:57 -0400132 fname = self.file
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400133 if fname and fname[0] != '#':
terryjreedy349abd92017-07-07 16:00:57 -0400134 if not self.IsEmpty():
135 try:
136 cfgFile = open(fname, 'w')
137 except OSError:
138 os.unlink(fname)
139 cfgFile = open(fname, 'w')
140 with cfgFile:
141 self.write(cfgFile)
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400142 elif os.path.exists(self.file):
143 os.remove(self.file)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000144
145class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400146 """Hold config parsers for all idle config files in singleton instance.
147
148 Default config files, self.defaultCfg --
149 for config_type in self.config_types:
150 (idle install dir)/config-{config-type}.def
151
152 User config files, self.userCfg --
153 for config_type in self.config_types:
154 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000155 """
Louie Luf776eb02017-07-19 05:17:56 +0800156 def __init__(self, _utest=False):
terryjreedy349abd92017-07-07 16:00:57 -0400157 self.config_types = ('main', 'highlight', 'keys', 'extensions')
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400158 self.defaultCfg = {}
159 self.userCfg = {}
160 self.cfg = {} # TODO use to select userCfg vs defaultCfg
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__)
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400169 self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir()
Cheryl Sabellaf8d4cc72019-07-16 16:58:25 -0400170 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(
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400174 os.path.join(userdir or '#', 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 Reedy0048afc2019-09-16 19:04:21 -0400185 if not idlelib.testing:
186 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
187 userDir + ',\n but the path does not exist.')
188 try:
189 print(warn, file=sys.stderr)
190 except OSError:
191 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000192 userDir = '~'
193 if userDir == "~": # still no path to home!
194 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
195 userDir = os.getcwd()
196 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000197 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000198 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000199 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200200 except OSError:
Louie Luf776eb02017-07-19 05:17:56 +0800201 if not idlelib.testing:
Terry Jan Reedy0048afc2019-09-16 19:04:21 -0400202 warn = ('\n Warning: unable to create user config directory\n' +
203 userDir + '\n Check path and permissions.\n Exiting!\n')
204 try:
205 print(warn, file=sys.stderr)
206 except OSError:
207 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000208 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400209 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000210 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000211
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000212 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000213 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400214 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000215
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400216 If type is not None, return a value of that type. Also pass raw
217 to the config parser. First try to return a valid value
218 (including type) from a user configuration. If that fails, try
219 the default configuration. If that fails, return default, with a
220 default of None.
221
222 Warn if either user or default configurations have an invalid value.
223 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000224 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200225 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400226 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200227 return self.userCfg[configType].Get(section, option,
228 type=type, raw=raw)
229 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400230 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200231 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400232 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200233 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400234 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400235 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200236 try:
237 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400238 return self.defaultCfg[configType].Get(
239 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200240 except ValueError:
241 pass
242 #returning default, print warning
243 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400244 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200245 ' problem retrieving configuration option %r\n'
246 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400247 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200248 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400249 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200250 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000251
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000252 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400253 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000254 self.userCfg[configType].SetOption(section, option, value)
255
Steven M. Gava2a63a072001-10-26 06:50:54 +0000256 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400257 """Return sections for configSet configType configuration.
258
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000259 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400260 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000261 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400262 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000263 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000264 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400265 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000266 elif configSet == 'default':
267 cfgParser=self.defaultCfg[configType]
268 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000269 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000270 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000271
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400272 def GetHighlight(self, theme, element):
273 """Return dict of theme element highlight colors.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400274
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400275 The keys are 'foreground' and 'background'. The values are
276 tkinter color strings for configuring backgrounds and tags.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000277 """
Terry Jan Reedyc1419572019-03-22 18:23:41 -0400278 cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
279 else 'user')
280 theme_dict = self.GetThemeDict(cfg, theme)
281 fore = theme_dict[element + '-foreground']
282 if element == 'cursor':
283 element = 'normal'
284 back = theme_dict[element + '-background']
285 return {"foreground": fore, "background": back}
Steven M. Gava9f25e672002-02-11 02:51:18 +0000286
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400287 def GetThemeDict(self, type, themeName):
288 """Return {option:value} dict for elements in themeName.
289
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000290 type - string, 'default' or 'user' theme type
291 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400292 Values are loaded over ultimate fallback defaults to guarantee
293 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000294 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000295 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400296 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000297 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400298 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000299 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000300 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400301 # Provide foreground and background colors for each theme
302 # element (other than cursor) even though some values are not
303 # yet used by idle, to allow for their use in the future.
304 # Default values are generally black and white.
305 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400306 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307 'normal-background':'#ffffff',
308 'keyword-foreground':'#000000',
309 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000310 'builtin-foreground':'#000000',
311 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000312 'comment-foreground':'#000000',
313 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000314 'string-foreground':'#000000',
315 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000316 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000317 'definition-background':'#ffffff',
318 'hilite-foreground':'#000000',
319 'hilite-background':'gray',
320 'break-foreground':'#ffffff',
321 'break-background':'#000000',
322 'hit-foreground':'#ffffff',
323 'hit-background':'#000000',
324 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000325 'error-background':'#000000',
Tal Einat7123ea02019-07-23 15:22:11 +0300326 'context-foreground':'#000000',
327 'context-background':'#ffffff',
328 'linenumber-foreground':'#000000',
329 'linenumber-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000330 #cursor (only foreground can be set)
331 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000332 #shell window
333 'stdout-foreground':'#000000',
334 'stdout-background':'#ffffff',
335 'stderr-foreground':'#000000',
336 'stderr-background':'#ffffff',
337 'console-foreground':'#000000',
wohlganger58fc71c2017-09-10 16:19:47 -0500338 'console-background':'#ffffff',
339 }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000340 for element in theme:
Tal Einat7123ea02019-07-23 15:22:11 +0300341 if not (cfgParser.has_option(themeName, element) or
342 # Skip warning for new elements.
343 element.startswith(('context-', 'linenumber-'))):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400344 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400345 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000346 ' -\n problem retrieving theme element %r'
347 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400348 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000349 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400350 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400351 theme[element] = cfgParser.Get(
352 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000353 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000354
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000355 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400356 "Return the name of the currently active text color theme."
357 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500358
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400359 def CurrentKeys(self):
360 """Return the name of the currently active key set."""
361 return self.current_colors_and_keys('Keys')
362
363 def current_colors_and_keys(self, section):
364 """Return the currently active name for Theme or Keys section.
365
366 idlelib.config-main.def ('default') includes these sections
367
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500368 [Theme]
369 default= 1
370 name= IDLE Classic
371 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500372
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400373 [Keys]
374 default= 1
375 name=
376 name2=
377
378 Item 'name2', is used for built-in ('default') themes and keys
379 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
380 because setting 'name' to a builtin not defined in older IDLEs
381 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500382 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400383 When default = True, 'name2' takes precedence over 'name',
384 while older IDLEs will just use name. When default = False,
385 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500386 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400387 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400388 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500389 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400390 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500391 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400392 name = self.GetOption('main', section, 'name2', default='')
393 if not name:
394 name = self.GetOption('main', section, 'name', default='')
395 if name:
396 source = self.defaultCfg if default else self.userCfg
397 if source[cfgname].has_section(name):
398 return name
399 return "IDLE Classic" if section == 'Theme' else self.default_keys()
400
401 @staticmethod
402 def default_keys():
403 if sys.platform[:3] == 'win':
404 return 'IDLE Classic Windows'
405 elif sys.platform == 'darwin':
406 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500407 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000409
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400410 def GetExtensions(self, active_only=True,
411 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400412 """Return extensions in default and user config-extensions files.
413
414 If active_only True, only return active (enabled) extensions
415 and optionally only editor or shell extensions.
416 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000417 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400418 extns = self.RemoveKeyBindNames(
419 self.GetSectionList('default', 'extensions'))
420 userExtns = self.RemoveKeyBindNames(
421 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000422 for extn in userExtns:
423 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000424 extns.append(extn)
wohlganger58fc71c2017-09-10 16:19:47 -0500425 for extn in ('AutoComplete','CodeContext',
426 'FormatParagraph','ParenMatch'):
427 extns.remove(extn)
428 # specific exclusions because we are storing config for mainlined old
429 # extensions in config-extensions.def for backward compatibility
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000430 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400431 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000432 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000433 if self.GetOption('extensions', extn, 'enable', default=True,
434 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000435 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400436 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000437 if editor_only:
438 option = "enable_editor"
439 else:
440 option = "enable_shell"
441 if self.GetOption('extensions', extn,option,
442 default=True, type='bool',
443 warn_on_default=False):
444 activeExtns.append(extn)
445 else:
446 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000447 return activeExtns
448 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000449 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000450
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400451 def RemoveKeyBindNames(self, extnNameList):
452 "Return extnNameList with keybinding section names removed."
Louie Luf776eb02017-07-19 05:17:56 +0800453 return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000454
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400455 def GetExtnNameForEvent(self, virtualEvent):
456 """Return the name of the extension binding virtualEvent, or None.
457
458 virtualEvent - string, name of the virtual event to test for,
459 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000460 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400461 extName = None
462 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000463 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000464 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000465 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400466 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000467 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000468
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400469 def GetExtensionKeys(self, extensionName):
470 """Return dict: {configurable extensionName event : active keybinding}.
471
472 Events come from default config extension_cfgBindings section.
473 Keybindings come from GetCurrentKeySet() active key dict,
474 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000475 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400476 keysName = extensionName + '_cfgBindings'
477 activeKeys = self.GetCurrentKeySet()
478 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000479 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400480 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000481 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400482 event = '<<' + eventName + '>>'
483 binding = activeKeys[event]
484 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000485 return extKeys
486
Steven M. Gavac628a062002-01-19 10:33:21 +0000487 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400488 """Return dict {configurable extensionName event : keybinding list}.
489
490 Events come from default config extension_cfgBindings section.
491 Keybindings list come from the splitting of GetOption, which
492 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000493 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400494 keysName = extensionName+'_cfgBindings'
495 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000496 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400497 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000498 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400499 binding = self.GetOption(
500 'extensions', keysName, eventName, default='').split()
501 event = '<<' + eventName + '>>'
502 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000503 return extKeys
504
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400505 def GetExtensionBindings(self, extensionName):
506 """Return dict {extensionName event : active or defined keybinding}.
507
508 Augment self.GetExtensionKeys(extensionName) with mapping of non-
509 configurable events (from default config) to GetOption splits,
510 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000511 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400512 bindsName = extensionName + '_bindings'
513 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000514 #add the non-configurable bindings
515 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400516 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000517 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400518 binding = self.GetOption(
519 'extensions', bindsName, eventName, default='').split()
520 event = '<<' + eventName + '>>'
521 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000522
523 return extBinds
524
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000525 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400526 """Return the keybinding list for keySetName eventStr.
527
528 keySetName - name of key binding set (config-keys section).
529 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000530 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400531 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400532 binding = self.GetOption('keys', keySetName, eventName, default='',
533 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000534 return binding
535
Steven M. Gavac628a062002-01-19 10:33:21 +0000536 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400537 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000538 result = self.GetKeySet(self.CurrentKeys())
539
Ned Deilyb7601672014-03-27 20:49:14 -0700540 if sys.platform == "darwin":
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500541 # macOS (OS X) Tk variants do not support the "Alt"
542 # keyboard modifier. Replace it with "Option".
543 # TODO (Ned?): the "Option" modifier does not work properly
544 # for Cocoa Tk and XQuartz Tk so we should not use it
545 # in the default 'OSX' keyset.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000546 for k, v in result.items():
547 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
548 if v != v2:
549 result[k] = v2
550
551 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000552
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400553 def GetKeySet(self, keySetName):
554 """Return event-key dict for keySetName core plus active extensions.
555
556 If a binding defined in an extension is already in use, the
557 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000558 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400559 keySet = self.GetCoreKeys(keySetName)
560 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000561 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400562 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000563 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000564 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000565 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000566 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400567 extKeys[event] = '' #disable this binding
568 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000569 return keySet
570
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400571 def IsCoreBinding(self, virtualEvent):
572 """Return True if the virtual event is one of the core idle key events.
573
574 virtualEvent - string, name of the virtual event to test for,
575 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000576 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000577 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000578
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400579# TODO make keyBindins a file or class attribute used for test above
wohlganger58fc71c2017-09-10 16:19:47 -0500580# and copied in function below.
581
582 former_extension_events = { # Those with user-configurable keys.
583 '<<force-open-completions>>', '<<expand-word>>',
584 '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400585 '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
586 '<<run-custom>>',
587 }
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400588
Steven M. Gavac628a062002-01-19 10:33:21 +0000589 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400590 """Return dict of core virtual-key keybindings for keySetName.
591
592 The default keySetName None corresponds to the keyBindings base
593 dict. If keySetName is not None, bindings from the config
594 file(s) are loaded _over_ these defaults, so if there is a
595 problem getting any core binding there will be an 'ultimate last
596 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000597 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000598 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000599 '<<copy>>': ['<Control-c>', '<Control-C>'],
600 '<<cut>>': ['<Control-x>', '<Control-X>'],
601 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000602 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
603 '<<center-insert>>': ['<Control-l>'],
604 '<<close-all-windows>>': ['<Control-q>'],
605 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000606 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000607 '<<end-of-file>>': ['<Control-d>'],
608 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000609 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000610 '<<history-next>>': ['<Alt-n>'],
611 '<<history-previous>>': ['<Alt-p>'],
612 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000613 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000614 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000615 '<<open-class-browser>>': ['<Alt-c>'],
616 '<<open-module>>': ['<Alt-m>'],
617 '<<open-new-window>>': ['<Control-n>'],
618 '<<open-window-from-file>>': ['<Control-o>'],
619 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000620 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000621 '<<redo>>': ['<Control-y>'],
622 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000623 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000624 '<<save-window-as-file>>': ['<Alt-s>'],
625 '<<save-window>>': ['<Control-s>'],
626 '<<select-all>>': ['<Alt-a>'],
627 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000628 '<<undo>>': ['<Control-z>'],
629 '<<find-again>>': ['<Control-g>', '<F3>'],
630 '<<find-in-files>>': ['<Alt-F3>'],
631 '<<find-selection>>': ['<Control-F3>'],
632 '<<find>>': ['<Control-f>'],
633 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000634 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000635 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300636 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000637 '<<smart-indent>>': ['<Key-Tab>'],
638 '<<indent-region>>': ['<Control-Key-bracketright>'],
639 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
640 '<<comment-region>>': ['<Alt-Key-3>'],
641 '<<uncomment-region>>': ['<Alt-Key-4>'],
642 '<<tabify-region>>': ['<Alt-Key-5>'],
643 '<<untabify-region>>': ['<Alt-Key-6>'],
644 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000645 '<<change-indentwidth>>': ['<Alt-Key-u>'],
646 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500647 '<<del-word-right>>': ['<Control-Key-Delete>'],
648 '<<force-open-completions>>': ['<Control-Key-space>'],
649 '<<expand-word>>': ['<Alt-Key-slash>'],
650 '<<force-open-calltip>>': ['<Control-Key-backslash>'],
651 '<<flash-paren>>': ['<Control-Key-0>'],
652 '<<format-paragraph>>': ['<Alt-Key-q>'],
653 '<<run-module>>': ['<Key-F5>'],
Cheryl Sabella201bc2d2019-06-17 22:24:10 -0400654 '<<run-custom>>': ['<Shift-Key-F5>'],
wohlganger58fc71c2017-09-10 16:19:47 -0500655 '<<check-module>>': ['<Alt-Key-x>'],
656 '<<zoom-height>>': ['<Alt-Key-2>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000657 }
wohlganger58fc71c2017-09-10 16:19:47 -0500658
Steven M. Gava17d01542001-12-03 00:37:28 +0000659 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400660 if not (self.userCfg['keys'].has_section(keySetName) or
661 self.defaultCfg['keys'].has_section(keySetName)):
662 warning = (
663 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
664 ' key set %r is not defined, using default bindings.' %
665 (keySetName,)
666 )
667 _warn(warning, 'keys', keySetName)
668 else:
669 for event in keyBindings:
670 binding = self.GetKeyBinding(keySetName, event)
671 if binding:
672 keyBindings[event] = binding
wohlganger58fc71c2017-09-10 16:19:47 -0500673 # Otherwise return default in keyBindings.
674 elif event not in self.former_extension_events:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400675 warning = (
676 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
677 ' problem retrieving key binding for event %r\n'
678 ' from key set %r.\n'
679 ' returning default value: %r' %
680 (event, keySetName, keyBindings[event])
681 )
682 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000683 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000684
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400685 def GetExtraHelpSourceList(self, configSet):
686 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000687
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000688 Valid configSets are 'user' or 'default'. Return a list of tuples of
689 the form (menu_item , path_to_help_file , option), or return the empty
690 list. 'option' is the sequence number of the help resource. 'option'
691 values determine the position of the menu items on the Help menu,
692 therefore the returned list must be sorted by 'option'.
693
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000694 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400695 helpSources = []
696 if configSet == 'user':
697 cfgParser = self.userCfg['main']
698 elif configSet == 'default':
699 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000700 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000701 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000702 options=cfgParser.GetOptionList('HelpFiles')
703 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400704 value=cfgParser.Get('HelpFiles', option, default=';')
705 if value.find(';') == -1: #malformed config entry with no ';'
706 menuItem = '' #make these empty
707 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000708 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000709 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000710 menuItem=value[0].strip()
711 helpPath=value[1].strip()
712 if menuItem and helpPath: #neither are empty strings
713 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000714 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000715 return helpSources
716
717 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400718 """Return a list of the details of all additional help sources.
719
720 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000721 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400722 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000723 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000724 return allHelpSources
725
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400726 def GetFont(self, root, configType, section):
727 """Retrieve a font from configuration (font, font-size, font-bold)
728 Intercept the special value 'TkFixedFont' and substitute
729 the actual font, factoring in some tweaks if needed for
730 appearance sakes.
731
732 The 'root' parameter can normally be any valid Tkinter widget.
733
734 Return a tuple (family, size, weight) suitable for passing
735 to tkinter.Font
736 """
737 family = self.GetOption(configType, section, 'font', default='courier')
738 size = self.GetOption(configType, section, 'font-size', type='int',
739 default='10')
740 bold = self.GetOption(configType, section, 'font-bold', default=0,
741 type='bool')
742 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400743 f = Font(name='TkFixedFont', exists=True, root=root)
744 actualFont = Font.actual(f)
745 family = actualFont['family']
746 size = actualFont['size']
747 if size <= 0:
748 size = 10 # if font in pixels, ignore actual size
749 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400750 return (family, size, 'bold' if bold else 'normal')
751
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000752 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400753 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000754 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000755 self.defaultCfg[key].Load()
756 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000757
758 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400759 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000760 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000762
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000763
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400764idleConf = IdleConf()
765
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400766_warned = set()
767def _warn(msg, *key):
768 key = (msg,) + key
769 if key not in _warned:
770 try:
771 print(msg, file=sys.stderr)
772 except OSError:
773 pass
774 _warned.add(key)
775
776
terryjreedy349abd92017-07-07 16:00:57 -0400777class ConfigChanges(dict):
778 """Manage a user's proposed configuration option changes.
779
780 Names used across multiple methods:
781 page -- one of the 4 top-level dicts representing a
782 .idlerc/config-x.cfg file.
783 config_type -- name of a page.
784 section -- a section within a page/file.
785 option -- name of an option within a section.
786 value -- value for the option.
787
788 Methods
789 add_option: Add option and value to changes.
790 save_option: Save option and value to config parser.
791 save_all: Save all the changes to the config parser and file.
csabella6d13b222017-07-11 19:09:44 -0400792 delete_section: If section exists,
793 delete from changes, userCfg, and file.
terryjreedy349abd92017-07-07 16:00:57 -0400794 clear: Clear all changes by clearing each page.
795 """
796 def __init__(self):
797 "Create a page for each configuration file"
798 self.pages = [] # List of unhashable dicts.
799 for config_type in idleConf.config_types:
800 self[config_type] = {}
801 self.pages.append(self[config_type])
802
803 def add_option(self, config_type, section, item, value):
804 "Add item/value pair for config_type and section."
805 page = self[config_type]
806 value = str(value) # Make sure we use a string.
807 if section not in page:
808 page[section] = {}
809 page[section][item] = value
810
811 @staticmethod
812 def save_option(config_type, section, item, value):
813 """Return True if the configuration value was added or changed.
814
815 Helper for save_all.
816 """
817 if idleConf.defaultCfg[config_type].has_option(section, item):
818 if idleConf.defaultCfg[config_type].Get(section, item) == value:
819 # The setting equals a default setting, remove it from user cfg.
820 return idleConf.userCfg[config_type].RemoveOption(section, item)
821 # If we got here, set the option.
822 return idleConf.userCfg[config_type].SetOption(section, item, value)
823
824 def save_all(self):
825 """Save configuration changes to the user config file.
826
Louie Lu50c94352017-07-13 02:05:32 +0800827 Clear self in preparation for additional changes.
828 Return changed for testing.
terryjreedy349abd92017-07-07 16:00:57 -0400829 """
830 idleConf.userCfg['main'].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800831
832 changed = False
terryjreedy349abd92017-07-07 16:00:57 -0400833 for config_type in self:
834 cfg_type_changed = False
835 page = self[config_type]
836 for section in page:
837 if section == 'HelpFiles': # Remove it for replacement.
838 idleConf.userCfg['main'].remove_section('HelpFiles')
839 cfg_type_changed = True
840 for item, value in page[section].items():
841 if self.save_option(config_type, section, item, value):
842 cfg_type_changed = True
843 if cfg_type_changed:
844 idleConf.userCfg[config_type].Save()
Louie Lu50c94352017-07-13 02:05:32 +0800845 changed = True
terryjreedy349abd92017-07-07 16:00:57 -0400846 for config_type in ['keys', 'highlight']:
847 # Save these even if unchanged!
848 idleConf.userCfg[config_type].Save()
849 self.clear()
850 # ConfigDialog caller must add the following call
851 # self.save_all_changed_extensions() # Uses a different mechanism.
Louie Lu50c94352017-07-13 02:05:32 +0800852 return changed
terryjreedy349abd92017-07-07 16:00:57 -0400853
854 def delete_section(self, config_type, section):
855 """Delete a section from self, userCfg, and file.
856
857 Used to delete custom themes and keysets.
858 """
859 if section in self[config_type]:
860 del self[config_type][section]
861 configpage = idleConf.userCfg[config_type]
862 configpage.remove_section(section)
863 configpage.Save()
864
865 def clear(self):
866 """Clear all 4 pages.
867
868 Called in save_all after saving to idleConf.
869 XXX Mark window *title* when there are changes; unmark here.
870 """
871 for page in self.pages:
872 page.clear()
873
874
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400875# TODO Revise test output, write expanded unittest
terryjreedy349abd92017-07-07 16:00:57 -0400876def _dump(): # htest # (not really, but ignore in coverage)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400877 from zlib import crc32
878 line, crc = 0, 0
879
880 def sprint(obj):
881 global line, crc
882 txt = str(obj)
883 line += 1
884 crc = crc32(txt.encode(encoding='utf-8'), crc)
885 print(txt)
terryjreedy349abd92017-07-07 16:00:57 -0400886 #print('***', line, crc, '***') # Uncomment for diagnosis.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400887
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000888 def dumpCfg(cfg):
terryjreedy349abd92017-07-07 16:00:57 -0400889 print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400890 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400891 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400892 sprint(key)
893 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000894 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400895 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400896 sprint(section)
897 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000898 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400899 sprint(option + ' = ' + cfg[key].Get(section, option))
900
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000901 dumpCfg(idleConf.defaultCfg)
902 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400903 print('\nlines = ', line, ', crc = ', crc, sep='')
terryjreedy349abd92017-07-07 16:00:57 -0400904
905if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400906 from unittest import main
907 main('idlelib.idle_test.test_config', verbosity=2, exit=False)
908
909 # Run revised _dump() as htest?