17db96d56Sopenharmony_ci"""idlelib.config -- Manage IDLE configuration information.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciThe comments at the beginning of config-main.def describe the
47db96d56Sopenharmony_ciconfiguration files and the design implemented to update user
57db96d56Sopenharmony_ciconfiguration information.  In particular, user configuration choices
67db96d56Sopenharmony_ciwhich duplicate the defaults will be removed from the user's
77db96d56Sopenharmony_ciconfiguration files, and if a user file becomes empty, it will be
87db96d56Sopenharmony_cideleted.
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ciThe configuration database maps options to values.  Conceptually, the
117db96d56Sopenharmony_cidatabase keys are tuples (config-type, section, item).  As implemented,
127db96d56Sopenharmony_cithere are  separate dicts for default and user values.  Each has
137db96d56Sopenharmony_ciconfig-type keys 'main', 'extensions', 'highlight', and 'keys'.  The
147db96d56Sopenharmony_civalue for each key is a ConfigParser instance that maps section and item
157db96d56Sopenharmony_cito values.  For 'main' and 'extensions', user values override
167db96d56Sopenharmony_cidefault values.  For 'highlight' and 'keys', user sections augment the
177db96d56Sopenharmony_cidefault sections (and must, therefore, have distinct names).
187db96d56Sopenharmony_ci
197db96d56Sopenharmony_ciThroughout this module there is an emphasis on returning usable defaults
207db96d56Sopenharmony_ciwhen a problem occurs in returning a requested configuration value back to
217db96d56Sopenharmony_ciidle. This is to allow IDLE to continue to function in spite of errors in
227db96d56Sopenharmony_cithe retrieval of config information. When a default is returned instead of
237db96d56Sopenharmony_cia requested config value, a message is printed to stderr to aid in
247db96d56Sopenharmony_ciconfiguration problem notification and resolution.
257db96d56Sopenharmony_ci"""
267db96d56Sopenharmony_ci# TODOs added Oct 2014, tjr
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_cifrom configparser import ConfigParser
297db96d56Sopenharmony_ciimport os
307db96d56Sopenharmony_ciimport sys
317db96d56Sopenharmony_ci
327db96d56Sopenharmony_cifrom tkinter.font import Font
337db96d56Sopenharmony_ciimport idlelib
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ciclass InvalidConfigType(Exception): pass
367db96d56Sopenharmony_ciclass InvalidConfigSet(Exception): pass
377db96d56Sopenharmony_ciclass InvalidTheme(Exception): pass
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_ciclass IdleConfParser(ConfigParser):
407db96d56Sopenharmony_ci    """
417db96d56Sopenharmony_ci    A ConfigParser specialised for idle configuration file handling
427db96d56Sopenharmony_ci    """
437db96d56Sopenharmony_ci    def __init__(self, cfgFile, cfgDefaults=None):
447db96d56Sopenharmony_ci        """
457db96d56Sopenharmony_ci        cfgFile - string, fully specified configuration file name
467db96d56Sopenharmony_ci        """
477db96d56Sopenharmony_ci        self.file = cfgFile  # This is currently '' when testing.
487db96d56Sopenharmony_ci        ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci    def Get(self, section, option, type=None, default=None, raw=False):
517db96d56Sopenharmony_ci        """
527db96d56Sopenharmony_ci        Get an option value for given section/option or return default.
537db96d56Sopenharmony_ci        If type is specified, return as type.
547db96d56Sopenharmony_ci        """
557db96d56Sopenharmony_ci        # TODO Use default as fallback, at least if not None
567db96d56Sopenharmony_ci        # Should also print Warning(file, section, option).
577db96d56Sopenharmony_ci        # Currently may raise ValueError
587db96d56Sopenharmony_ci        if not self.has_option(section, option):
597db96d56Sopenharmony_ci            return default
607db96d56Sopenharmony_ci        if type == 'bool':
617db96d56Sopenharmony_ci            return self.getboolean(section, option)
627db96d56Sopenharmony_ci        elif type == 'int':
637db96d56Sopenharmony_ci            return self.getint(section, option)
647db96d56Sopenharmony_ci        else:
657db96d56Sopenharmony_ci            return self.get(section, option, raw=raw)
667db96d56Sopenharmony_ci
677db96d56Sopenharmony_ci    def GetOptionList(self, section):
687db96d56Sopenharmony_ci        "Return a list of options for given section, else []."
697db96d56Sopenharmony_ci        if self.has_section(section):
707db96d56Sopenharmony_ci            return self.options(section)
717db96d56Sopenharmony_ci        else:  #return a default value
727db96d56Sopenharmony_ci            return []
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci    def Load(self):
757db96d56Sopenharmony_ci        "Load the configuration file from disk."
767db96d56Sopenharmony_ci        if self.file:
777db96d56Sopenharmony_ci            self.read(self.file)
787db96d56Sopenharmony_ci
797db96d56Sopenharmony_ciclass IdleUserConfParser(IdleConfParser):
807db96d56Sopenharmony_ci    """
817db96d56Sopenharmony_ci    IdleConfigParser specialised for user configuration handling.
827db96d56Sopenharmony_ci    """
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ci    def SetOption(self, section, option, value):
857db96d56Sopenharmony_ci        """Return True if option is added or changed to value, else False.
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci        Add section if required.  False means option already had value.
887db96d56Sopenharmony_ci        """
897db96d56Sopenharmony_ci        if self.has_option(section, option):
907db96d56Sopenharmony_ci            if self.get(section, option) == value:
917db96d56Sopenharmony_ci                return False
927db96d56Sopenharmony_ci            else:
937db96d56Sopenharmony_ci                self.set(section, option, value)
947db96d56Sopenharmony_ci                return True
957db96d56Sopenharmony_ci        else:
967db96d56Sopenharmony_ci            if not self.has_section(section):
977db96d56Sopenharmony_ci                self.add_section(section)
987db96d56Sopenharmony_ci            self.set(section, option, value)
997db96d56Sopenharmony_ci            return True
1007db96d56Sopenharmony_ci
1017db96d56Sopenharmony_ci    def RemoveOption(self, section, option):
1027db96d56Sopenharmony_ci        """Return True if option is removed from section, else False.
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci        False if either section does not exist or did not have option.
1057db96d56Sopenharmony_ci        """
1067db96d56Sopenharmony_ci        if self.has_section(section):
1077db96d56Sopenharmony_ci            return self.remove_option(section, option)
1087db96d56Sopenharmony_ci        return False
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_ci    def AddSection(self, section):
1117db96d56Sopenharmony_ci        "If section doesn't exist, add it."
1127db96d56Sopenharmony_ci        if not self.has_section(section):
1137db96d56Sopenharmony_ci            self.add_section(section)
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ci    def RemoveEmptySections(self):
1167db96d56Sopenharmony_ci        "Remove any sections that have no options."
1177db96d56Sopenharmony_ci        for section in self.sections():
1187db96d56Sopenharmony_ci            if not self.GetOptionList(section):
1197db96d56Sopenharmony_ci                self.remove_section(section)
1207db96d56Sopenharmony_ci
1217db96d56Sopenharmony_ci    def IsEmpty(self):
1227db96d56Sopenharmony_ci        "Return True if no sections after removing empty sections."
1237db96d56Sopenharmony_ci        self.RemoveEmptySections()
1247db96d56Sopenharmony_ci        return not self.sections()
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci    def Save(self):
1277db96d56Sopenharmony_ci        """Update user configuration file.
1287db96d56Sopenharmony_ci
1297db96d56Sopenharmony_ci        If self not empty after removing empty sections, write the file
1307db96d56Sopenharmony_ci        to disk. Otherwise, remove the file from disk if it exists.
1317db96d56Sopenharmony_ci        """
1327db96d56Sopenharmony_ci        fname = self.file
1337db96d56Sopenharmony_ci        if fname and fname[0] != '#':
1347db96d56Sopenharmony_ci            if not self.IsEmpty():
1357db96d56Sopenharmony_ci                try:
1367db96d56Sopenharmony_ci                    cfgFile = open(fname, 'w')
1377db96d56Sopenharmony_ci                except OSError:
1387db96d56Sopenharmony_ci                    os.unlink(fname)
1397db96d56Sopenharmony_ci                    cfgFile = open(fname, 'w')
1407db96d56Sopenharmony_ci                with cfgFile:
1417db96d56Sopenharmony_ci                    self.write(cfgFile)
1427db96d56Sopenharmony_ci            elif os.path.exists(self.file):
1437db96d56Sopenharmony_ci                os.remove(self.file)
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ciclass IdleConf:
1467db96d56Sopenharmony_ci    """Hold config parsers for all idle config files in singleton instance.
1477db96d56Sopenharmony_ci
1487db96d56Sopenharmony_ci    Default config files, self.defaultCfg --
1497db96d56Sopenharmony_ci        for config_type in self.config_types:
1507db96d56Sopenharmony_ci            (idle install dir)/config-{config-type}.def
1517db96d56Sopenharmony_ci
1527db96d56Sopenharmony_ci    User config files, self.userCfg --
1537db96d56Sopenharmony_ci        for config_type in self.config_types:
1547db96d56Sopenharmony_ci        (user home dir)/.idlerc/config-{config-type}.cfg
1557db96d56Sopenharmony_ci    """
1567db96d56Sopenharmony_ci    def __init__(self, _utest=False):
1577db96d56Sopenharmony_ci        self.config_types = ('main', 'highlight', 'keys', 'extensions')
1587db96d56Sopenharmony_ci        self.defaultCfg = {}
1597db96d56Sopenharmony_ci        self.userCfg = {}
1607db96d56Sopenharmony_ci        self.cfg = {}  # TODO use to select userCfg vs defaultCfg
1617db96d56Sopenharmony_ci        # self.blink_off_time = <first editor text>['insertofftime']
1627db96d56Sopenharmony_ci        # See https:/bugs.python.org/issue4630, msg356516.
1637db96d56Sopenharmony_ci
1647db96d56Sopenharmony_ci        if not _utest:
1657db96d56Sopenharmony_ci            self.CreateConfigHandlers()
1667db96d56Sopenharmony_ci            self.LoadCfgFiles()
1677db96d56Sopenharmony_ci
1687db96d56Sopenharmony_ci    def CreateConfigHandlers(self):
1697db96d56Sopenharmony_ci        "Populate default and user config parser dictionaries."
1707db96d56Sopenharmony_ci        idledir = os.path.dirname(__file__)
1717db96d56Sopenharmony_ci        self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir()
1727db96d56Sopenharmony_ci        for cfg_type in self.config_types:
1737db96d56Sopenharmony_ci            self.defaultCfg[cfg_type] = IdleConfParser(
1747db96d56Sopenharmony_ci                os.path.join(idledir, f'config-{cfg_type}.def'))
1757db96d56Sopenharmony_ci            self.userCfg[cfg_type] = IdleUserConfParser(
1767db96d56Sopenharmony_ci                os.path.join(userdir or '#', f'config-{cfg_type}.cfg'))
1777db96d56Sopenharmony_ci
1787db96d56Sopenharmony_ci    def GetUserCfgDir(self):
1797db96d56Sopenharmony_ci        """Return a filesystem directory for storing user config files.
1807db96d56Sopenharmony_ci
1817db96d56Sopenharmony_ci        Creates it if required.
1827db96d56Sopenharmony_ci        """
1837db96d56Sopenharmony_ci        cfgDir = '.idlerc'
1847db96d56Sopenharmony_ci        userDir = os.path.expanduser('~')
1857db96d56Sopenharmony_ci        if userDir != '~': # expanduser() found user home dir
1867db96d56Sopenharmony_ci            if not os.path.exists(userDir):
1877db96d56Sopenharmony_ci                if not idlelib.testing:
1887db96d56Sopenharmony_ci                    warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
1897db96d56Sopenharmony_ci                            userDir + ',\n but the path does not exist.')
1907db96d56Sopenharmony_ci                    try:
1917db96d56Sopenharmony_ci                        print(warn, file=sys.stderr)
1927db96d56Sopenharmony_ci                    except OSError:
1937db96d56Sopenharmony_ci                        pass
1947db96d56Sopenharmony_ci                userDir = '~'
1957db96d56Sopenharmony_ci        if userDir == "~": # still no path to home!
1967db96d56Sopenharmony_ci            # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
1977db96d56Sopenharmony_ci            userDir = os.getcwd()
1987db96d56Sopenharmony_ci        userDir = os.path.join(userDir, cfgDir)
1997db96d56Sopenharmony_ci        if not os.path.exists(userDir):
2007db96d56Sopenharmony_ci            try:
2017db96d56Sopenharmony_ci                os.mkdir(userDir)
2027db96d56Sopenharmony_ci            except OSError:
2037db96d56Sopenharmony_ci                if not idlelib.testing:
2047db96d56Sopenharmony_ci                    warn = ('\n Warning: unable to create user config directory\n' +
2057db96d56Sopenharmony_ci                            userDir + '\n Check path and permissions.\n Exiting!\n')
2067db96d56Sopenharmony_ci                    try:
2077db96d56Sopenharmony_ci                        print(warn, file=sys.stderr)
2087db96d56Sopenharmony_ci                    except OSError:
2097db96d56Sopenharmony_ci                        pass
2107db96d56Sopenharmony_ci                raise SystemExit
2117db96d56Sopenharmony_ci        # TODO continue without userDIr instead of exit
2127db96d56Sopenharmony_ci        return userDir
2137db96d56Sopenharmony_ci
2147db96d56Sopenharmony_ci    def GetOption(self, configType, section, option, default=None, type=None,
2157db96d56Sopenharmony_ci                  warn_on_default=True, raw=False):
2167db96d56Sopenharmony_ci        """Return a value for configType section option, or default.
2177db96d56Sopenharmony_ci
2187db96d56Sopenharmony_ci        If type is not None, return a value of that type.  Also pass raw
2197db96d56Sopenharmony_ci        to the config parser.  First try to return a valid value
2207db96d56Sopenharmony_ci        (including type) from a user configuration. If that fails, try
2217db96d56Sopenharmony_ci        the default configuration. If that fails, return default, with a
2227db96d56Sopenharmony_ci        default of None.
2237db96d56Sopenharmony_ci
2247db96d56Sopenharmony_ci        Warn if either user or default configurations have an invalid value.
2257db96d56Sopenharmony_ci        Warn if default is returned and warn_on_default is True.
2267db96d56Sopenharmony_ci        """
2277db96d56Sopenharmony_ci        try:
2287db96d56Sopenharmony_ci            if self.userCfg[configType].has_option(section, option):
2297db96d56Sopenharmony_ci                return self.userCfg[configType].Get(section, option,
2307db96d56Sopenharmony_ci                                                    type=type, raw=raw)
2317db96d56Sopenharmony_ci        except ValueError:
2327db96d56Sopenharmony_ci            warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
2337db96d56Sopenharmony_ci                       ' invalid %r value for configuration option %r\n'
2347db96d56Sopenharmony_ci                       ' from section %r: %r' %
2357db96d56Sopenharmony_ci                       (type, option, section,
2367db96d56Sopenharmony_ci                       self.userCfg[configType].Get(section, option, raw=raw)))
2377db96d56Sopenharmony_ci            _warn(warning, configType, section, option)
2387db96d56Sopenharmony_ci        try:
2397db96d56Sopenharmony_ci            if self.defaultCfg[configType].has_option(section,option):
2407db96d56Sopenharmony_ci                return self.defaultCfg[configType].Get(
2417db96d56Sopenharmony_ci                        section, option, type=type, raw=raw)
2427db96d56Sopenharmony_ci        except ValueError:
2437db96d56Sopenharmony_ci            pass
2447db96d56Sopenharmony_ci        #returning default, print warning
2457db96d56Sopenharmony_ci        if warn_on_default:
2467db96d56Sopenharmony_ci            warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
2477db96d56Sopenharmony_ci                       ' problem retrieving configuration option %r\n'
2487db96d56Sopenharmony_ci                       ' from section %r.\n'
2497db96d56Sopenharmony_ci                       ' returning default value: %r' %
2507db96d56Sopenharmony_ci                       (option, section, default))
2517db96d56Sopenharmony_ci            _warn(warning, configType, section, option)
2527db96d56Sopenharmony_ci        return default
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci    def SetOption(self, configType, section, option, value):
2557db96d56Sopenharmony_ci        """Set section option to value in user config file."""
2567db96d56Sopenharmony_ci        self.userCfg[configType].SetOption(section, option, value)
2577db96d56Sopenharmony_ci
2587db96d56Sopenharmony_ci    def GetSectionList(self, configSet, configType):
2597db96d56Sopenharmony_ci        """Return sections for configSet configType configuration.
2607db96d56Sopenharmony_ci
2617db96d56Sopenharmony_ci        configSet must be either 'user' or 'default'
2627db96d56Sopenharmony_ci        configType must be in self.config_types.
2637db96d56Sopenharmony_ci        """
2647db96d56Sopenharmony_ci        if not (configType in self.config_types):
2657db96d56Sopenharmony_ci            raise InvalidConfigType('Invalid configType specified')
2667db96d56Sopenharmony_ci        if configSet == 'user':
2677db96d56Sopenharmony_ci            cfgParser = self.userCfg[configType]
2687db96d56Sopenharmony_ci        elif configSet == 'default':
2697db96d56Sopenharmony_ci            cfgParser=self.defaultCfg[configType]
2707db96d56Sopenharmony_ci        else:
2717db96d56Sopenharmony_ci            raise InvalidConfigSet('Invalid configSet specified')
2727db96d56Sopenharmony_ci        return cfgParser.sections()
2737db96d56Sopenharmony_ci
2747db96d56Sopenharmony_ci    def GetHighlight(self, theme, element):
2757db96d56Sopenharmony_ci        """Return dict of theme element highlight colors.
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci        The keys are 'foreground' and 'background'.  The values are
2787db96d56Sopenharmony_ci        tkinter color strings for configuring backgrounds and tags.
2797db96d56Sopenharmony_ci        """
2807db96d56Sopenharmony_ci        cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
2817db96d56Sopenharmony_ci               else 'user')
2827db96d56Sopenharmony_ci        theme_dict = self.GetThemeDict(cfg, theme)
2837db96d56Sopenharmony_ci        fore = theme_dict[element + '-foreground']
2847db96d56Sopenharmony_ci        if element == 'cursor':
2857db96d56Sopenharmony_ci            element = 'normal'
2867db96d56Sopenharmony_ci        back = theme_dict[element + '-background']
2877db96d56Sopenharmony_ci        return {"foreground": fore, "background": back}
2887db96d56Sopenharmony_ci
2897db96d56Sopenharmony_ci    def GetThemeDict(self, type, themeName):
2907db96d56Sopenharmony_ci        """Return {option:value} dict for elements in themeName.
2917db96d56Sopenharmony_ci
2927db96d56Sopenharmony_ci        type - string, 'default' or 'user' theme type
2937db96d56Sopenharmony_ci        themeName - string, theme name
2947db96d56Sopenharmony_ci        Values are loaded over ultimate fallback defaults to guarantee
2957db96d56Sopenharmony_ci        that all theme elements are present in a newly created theme.
2967db96d56Sopenharmony_ci        """
2977db96d56Sopenharmony_ci        if type == 'user':
2987db96d56Sopenharmony_ci            cfgParser = self.userCfg['highlight']
2997db96d56Sopenharmony_ci        elif type == 'default':
3007db96d56Sopenharmony_ci            cfgParser = self.defaultCfg['highlight']
3017db96d56Sopenharmony_ci        else:
3027db96d56Sopenharmony_ci            raise InvalidTheme('Invalid theme type specified')
3037db96d56Sopenharmony_ci        # Provide foreground and background colors for each theme
3047db96d56Sopenharmony_ci        # element (other than cursor) even though some values are not
3057db96d56Sopenharmony_ci        # yet used by idle, to allow for their use in the future.
3067db96d56Sopenharmony_ci        # Default values are generally black and white.
3077db96d56Sopenharmony_ci        # TODO copy theme from a class attribute.
3087db96d56Sopenharmony_ci        theme ={'normal-foreground':'#000000',
3097db96d56Sopenharmony_ci                'normal-background':'#ffffff',
3107db96d56Sopenharmony_ci                'keyword-foreground':'#000000',
3117db96d56Sopenharmony_ci                'keyword-background':'#ffffff',
3127db96d56Sopenharmony_ci                'builtin-foreground':'#000000',
3137db96d56Sopenharmony_ci                'builtin-background':'#ffffff',
3147db96d56Sopenharmony_ci                'comment-foreground':'#000000',
3157db96d56Sopenharmony_ci                'comment-background':'#ffffff',
3167db96d56Sopenharmony_ci                'string-foreground':'#000000',
3177db96d56Sopenharmony_ci                'string-background':'#ffffff',
3187db96d56Sopenharmony_ci                'definition-foreground':'#000000',
3197db96d56Sopenharmony_ci                'definition-background':'#ffffff',
3207db96d56Sopenharmony_ci                'hilite-foreground':'#000000',
3217db96d56Sopenharmony_ci                'hilite-background':'gray',
3227db96d56Sopenharmony_ci                'break-foreground':'#ffffff',
3237db96d56Sopenharmony_ci                'break-background':'#000000',
3247db96d56Sopenharmony_ci                'hit-foreground':'#ffffff',
3257db96d56Sopenharmony_ci                'hit-background':'#000000',
3267db96d56Sopenharmony_ci                'error-foreground':'#ffffff',
3277db96d56Sopenharmony_ci                'error-background':'#000000',
3287db96d56Sopenharmony_ci                'context-foreground':'#000000',
3297db96d56Sopenharmony_ci                'context-background':'#ffffff',
3307db96d56Sopenharmony_ci                'linenumber-foreground':'#000000',
3317db96d56Sopenharmony_ci                'linenumber-background':'#ffffff',
3327db96d56Sopenharmony_ci                #cursor (only foreground can be set)
3337db96d56Sopenharmony_ci                'cursor-foreground':'#000000',
3347db96d56Sopenharmony_ci                #shell window
3357db96d56Sopenharmony_ci                'stdout-foreground':'#000000',
3367db96d56Sopenharmony_ci                'stdout-background':'#ffffff',
3377db96d56Sopenharmony_ci                'stderr-foreground':'#000000',
3387db96d56Sopenharmony_ci                'stderr-background':'#ffffff',
3397db96d56Sopenharmony_ci                'console-foreground':'#000000',
3407db96d56Sopenharmony_ci                'console-background':'#ffffff',
3417db96d56Sopenharmony_ci                }
3427db96d56Sopenharmony_ci        for element in theme:
3437db96d56Sopenharmony_ci            if not (cfgParser.has_option(themeName, element) or
3447db96d56Sopenharmony_ci                    # Skip warning for new elements.
3457db96d56Sopenharmony_ci                    element.startswith(('context-', 'linenumber-'))):
3467db96d56Sopenharmony_ci                # Print warning that will return a default color
3477db96d56Sopenharmony_ci                warning = ('\n Warning: config.IdleConf.GetThemeDict'
3487db96d56Sopenharmony_ci                           ' -\n problem retrieving theme element %r'
3497db96d56Sopenharmony_ci                           '\n from theme %r.\n'
3507db96d56Sopenharmony_ci                           ' returning default color: %r' %
3517db96d56Sopenharmony_ci                           (element, themeName, theme[element]))
3527db96d56Sopenharmony_ci                _warn(warning, 'highlight', themeName, element)
3537db96d56Sopenharmony_ci            theme[element] = cfgParser.Get(
3547db96d56Sopenharmony_ci                    themeName, element, default=theme[element])
3557db96d56Sopenharmony_ci        return theme
3567db96d56Sopenharmony_ci
3577db96d56Sopenharmony_ci    def CurrentTheme(self):
3587db96d56Sopenharmony_ci        "Return the name of the currently active text color theme."
3597db96d56Sopenharmony_ci        return self.current_colors_and_keys('Theme')
3607db96d56Sopenharmony_ci
3617db96d56Sopenharmony_ci    def CurrentKeys(self):
3627db96d56Sopenharmony_ci        """Return the name of the currently active key set."""
3637db96d56Sopenharmony_ci        return self.current_colors_and_keys('Keys')
3647db96d56Sopenharmony_ci
3657db96d56Sopenharmony_ci    def current_colors_and_keys(self, section):
3667db96d56Sopenharmony_ci        """Return the currently active name for Theme or Keys section.
3677db96d56Sopenharmony_ci
3687db96d56Sopenharmony_ci        idlelib.config-main.def ('default') includes these sections
3697db96d56Sopenharmony_ci
3707db96d56Sopenharmony_ci        [Theme]
3717db96d56Sopenharmony_ci        default= 1
3727db96d56Sopenharmony_ci        name= IDLE Classic
3737db96d56Sopenharmony_ci        name2=
3747db96d56Sopenharmony_ci
3757db96d56Sopenharmony_ci        [Keys]
3767db96d56Sopenharmony_ci        default= 1
3777db96d56Sopenharmony_ci        name=
3787db96d56Sopenharmony_ci        name2=
3797db96d56Sopenharmony_ci
3807db96d56Sopenharmony_ci        Item 'name2', is used for built-in ('default') themes and keys
3817db96d56Sopenharmony_ci        added after 2015 Oct 1 and 2016 July 1.  This kludge is needed
3827db96d56Sopenharmony_ci        because setting 'name' to a builtin not defined in older IDLEs
3837db96d56Sopenharmony_ci        to display multiple error messages or quit.
3847db96d56Sopenharmony_ci        See https://bugs.python.org/issue25313.
3857db96d56Sopenharmony_ci        When default = True, 'name2' takes precedence over 'name',
3867db96d56Sopenharmony_ci        while older IDLEs will just use name.  When default = False,
3877db96d56Sopenharmony_ci        'name2' may still be set, but it is ignored.
3887db96d56Sopenharmony_ci        """
3897db96d56Sopenharmony_ci        cfgname = 'highlight' if section == 'Theme' else 'keys'
3907db96d56Sopenharmony_ci        default = self.GetOption('main', section, 'default',
3917db96d56Sopenharmony_ci                                 type='bool', default=True)
3927db96d56Sopenharmony_ci        name = ''
3937db96d56Sopenharmony_ci        if default:
3947db96d56Sopenharmony_ci            name = self.GetOption('main', section, 'name2', default='')
3957db96d56Sopenharmony_ci        if not name:
3967db96d56Sopenharmony_ci            name = self.GetOption('main', section, 'name', default='')
3977db96d56Sopenharmony_ci        if name:
3987db96d56Sopenharmony_ci            source = self.defaultCfg if default else self.userCfg
3997db96d56Sopenharmony_ci            if source[cfgname].has_section(name):
4007db96d56Sopenharmony_ci                return name
4017db96d56Sopenharmony_ci        return "IDLE Classic" if section == 'Theme' else self.default_keys()
4027db96d56Sopenharmony_ci
4037db96d56Sopenharmony_ci    @staticmethod
4047db96d56Sopenharmony_ci    def default_keys():
4057db96d56Sopenharmony_ci        if sys.platform[:3] == 'win':
4067db96d56Sopenharmony_ci            return 'IDLE Classic Windows'
4077db96d56Sopenharmony_ci        elif sys.platform == 'darwin':
4087db96d56Sopenharmony_ci            return 'IDLE Classic OSX'
4097db96d56Sopenharmony_ci        else:
4107db96d56Sopenharmony_ci            return 'IDLE Modern Unix'
4117db96d56Sopenharmony_ci
4127db96d56Sopenharmony_ci    def GetExtensions(self, active_only=True,
4137db96d56Sopenharmony_ci                      editor_only=False, shell_only=False):
4147db96d56Sopenharmony_ci        """Return extensions in default and user config-extensions files.
4157db96d56Sopenharmony_ci
4167db96d56Sopenharmony_ci        If active_only True, only return active (enabled) extensions
4177db96d56Sopenharmony_ci        and optionally only editor or shell extensions.
4187db96d56Sopenharmony_ci        If active_only False, return all extensions.
4197db96d56Sopenharmony_ci        """
4207db96d56Sopenharmony_ci        extns = self.RemoveKeyBindNames(
4217db96d56Sopenharmony_ci                self.GetSectionList('default', 'extensions'))
4227db96d56Sopenharmony_ci        userExtns = self.RemoveKeyBindNames(
4237db96d56Sopenharmony_ci                self.GetSectionList('user', 'extensions'))
4247db96d56Sopenharmony_ci        for extn in userExtns:
4257db96d56Sopenharmony_ci            if extn not in extns: #user has added own extension
4267db96d56Sopenharmony_ci                extns.append(extn)
4277db96d56Sopenharmony_ci        for extn in ('AutoComplete','CodeContext',
4287db96d56Sopenharmony_ci                     'FormatParagraph','ParenMatch'):
4297db96d56Sopenharmony_ci            extns.remove(extn)
4307db96d56Sopenharmony_ci            # specific exclusions because we are storing config for mainlined old
4317db96d56Sopenharmony_ci            # extensions in config-extensions.def for backward compatibility
4327db96d56Sopenharmony_ci        if active_only:
4337db96d56Sopenharmony_ci            activeExtns = []
4347db96d56Sopenharmony_ci            for extn in extns:
4357db96d56Sopenharmony_ci                if self.GetOption('extensions', extn, 'enable', default=True,
4367db96d56Sopenharmony_ci                                  type='bool'):
4377db96d56Sopenharmony_ci                    #the extension is enabled
4387db96d56Sopenharmony_ci                    if editor_only or shell_only:  # TODO both True contradict
4397db96d56Sopenharmony_ci                        if editor_only:
4407db96d56Sopenharmony_ci                            option = "enable_editor"
4417db96d56Sopenharmony_ci                        else:
4427db96d56Sopenharmony_ci                            option = "enable_shell"
4437db96d56Sopenharmony_ci                        if self.GetOption('extensions', extn,option,
4447db96d56Sopenharmony_ci                                          default=True, type='bool',
4457db96d56Sopenharmony_ci                                          warn_on_default=False):
4467db96d56Sopenharmony_ci                            activeExtns.append(extn)
4477db96d56Sopenharmony_ci                    else:
4487db96d56Sopenharmony_ci                        activeExtns.append(extn)
4497db96d56Sopenharmony_ci            return activeExtns
4507db96d56Sopenharmony_ci        else:
4517db96d56Sopenharmony_ci            return extns
4527db96d56Sopenharmony_ci
4537db96d56Sopenharmony_ci    def RemoveKeyBindNames(self, extnNameList):
4547db96d56Sopenharmony_ci        "Return extnNameList with keybinding section names removed."
4557db96d56Sopenharmony_ci        return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
4567db96d56Sopenharmony_ci
4577db96d56Sopenharmony_ci    def GetExtnNameForEvent(self, virtualEvent):
4587db96d56Sopenharmony_ci        """Return the name of the extension binding virtualEvent, or None.
4597db96d56Sopenharmony_ci
4607db96d56Sopenharmony_ci        virtualEvent - string, name of the virtual event to test for,
4617db96d56Sopenharmony_ci                       without the enclosing '<< >>'
4627db96d56Sopenharmony_ci        """
4637db96d56Sopenharmony_ci        extName = None
4647db96d56Sopenharmony_ci        vEvent = '<<' + virtualEvent + '>>'
4657db96d56Sopenharmony_ci        for extn in self.GetExtensions(active_only=0):
4667db96d56Sopenharmony_ci            for event in self.GetExtensionKeys(extn):
4677db96d56Sopenharmony_ci                if event == vEvent:
4687db96d56Sopenharmony_ci                    extName = extn  # TODO return here?
4697db96d56Sopenharmony_ci        return extName
4707db96d56Sopenharmony_ci
4717db96d56Sopenharmony_ci    def GetExtensionKeys(self, extensionName):
4727db96d56Sopenharmony_ci        """Return dict: {configurable extensionName event : active keybinding}.
4737db96d56Sopenharmony_ci
4747db96d56Sopenharmony_ci        Events come from default config extension_cfgBindings section.
4757db96d56Sopenharmony_ci        Keybindings come from GetCurrentKeySet() active key dict,
4767db96d56Sopenharmony_ci        where previously used bindings are disabled.
4777db96d56Sopenharmony_ci        """
4787db96d56Sopenharmony_ci        keysName = extensionName + '_cfgBindings'
4797db96d56Sopenharmony_ci        activeKeys = self.GetCurrentKeySet()
4807db96d56Sopenharmony_ci        extKeys = {}
4817db96d56Sopenharmony_ci        if self.defaultCfg['extensions'].has_section(keysName):
4827db96d56Sopenharmony_ci            eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
4837db96d56Sopenharmony_ci            for eventName in eventNames:
4847db96d56Sopenharmony_ci                event = '<<' + eventName + '>>'
4857db96d56Sopenharmony_ci                binding = activeKeys[event]
4867db96d56Sopenharmony_ci                extKeys[event] = binding
4877db96d56Sopenharmony_ci        return extKeys
4887db96d56Sopenharmony_ci
4897db96d56Sopenharmony_ci    def __GetRawExtensionKeys(self,extensionName):
4907db96d56Sopenharmony_ci        """Return dict {configurable extensionName event : keybinding list}.
4917db96d56Sopenharmony_ci
4927db96d56Sopenharmony_ci        Events come from default config extension_cfgBindings section.
4937db96d56Sopenharmony_ci        Keybindings list come from the splitting of GetOption, which
4947db96d56Sopenharmony_ci        tries user config before default config.
4957db96d56Sopenharmony_ci        """
4967db96d56Sopenharmony_ci        keysName = extensionName+'_cfgBindings'
4977db96d56Sopenharmony_ci        extKeys = {}
4987db96d56Sopenharmony_ci        if self.defaultCfg['extensions'].has_section(keysName):
4997db96d56Sopenharmony_ci            eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
5007db96d56Sopenharmony_ci            for eventName in eventNames:
5017db96d56Sopenharmony_ci                binding = self.GetOption(
5027db96d56Sopenharmony_ci                        'extensions', keysName, eventName, default='').split()
5037db96d56Sopenharmony_ci                event = '<<' + eventName + '>>'
5047db96d56Sopenharmony_ci                extKeys[event] = binding
5057db96d56Sopenharmony_ci        return extKeys
5067db96d56Sopenharmony_ci
5077db96d56Sopenharmony_ci    def GetExtensionBindings(self, extensionName):
5087db96d56Sopenharmony_ci        """Return dict {extensionName event : active or defined keybinding}.
5097db96d56Sopenharmony_ci
5107db96d56Sopenharmony_ci        Augment self.GetExtensionKeys(extensionName) with mapping of non-
5117db96d56Sopenharmony_ci        configurable events (from default config) to GetOption splits,
5127db96d56Sopenharmony_ci        as in self.__GetRawExtensionKeys.
5137db96d56Sopenharmony_ci        """
5147db96d56Sopenharmony_ci        bindsName = extensionName + '_bindings'
5157db96d56Sopenharmony_ci        extBinds = self.GetExtensionKeys(extensionName)
5167db96d56Sopenharmony_ci        #add the non-configurable bindings
5177db96d56Sopenharmony_ci        if self.defaultCfg['extensions'].has_section(bindsName):
5187db96d56Sopenharmony_ci            eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
5197db96d56Sopenharmony_ci            for eventName in eventNames:
5207db96d56Sopenharmony_ci                binding = self.GetOption(
5217db96d56Sopenharmony_ci                        'extensions', bindsName, eventName, default='').split()
5227db96d56Sopenharmony_ci                event = '<<' + eventName + '>>'
5237db96d56Sopenharmony_ci                extBinds[event] = binding
5247db96d56Sopenharmony_ci
5257db96d56Sopenharmony_ci        return extBinds
5267db96d56Sopenharmony_ci
5277db96d56Sopenharmony_ci    def GetKeyBinding(self, keySetName, eventStr):
5287db96d56Sopenharmony_ci        """Return the keybinding list for keySetName eventStr.
5297db96d56Sopenharmony_ci
5307db96d56Sopenharmony_ci        keySetName - name of key binding set (config-keys section).
5317db96d56Sopenharmony_ci        eventStr - virtual event, including brackets, as in '<<event>>'.
5327db96d56Sopenharmony_ci        """
5337db96d56Sopenharmony_ci        eventName = eventStr[2:-2] #trim off the angle brackets
5347db96d56Sopenharmony_ci        binding = self.GetOption('keys', keySetName, eventName, default='',
5357db96d56Sopenharmony_ci                                 warn_on_default=False).split()
5367db96d56Sopenharmony_ci        return binding
5377db96d56Sopenharmony_ci
5387db96d56Sopenharmony_ci    def GetCurrentKeySet(self):
5397db96d56Sopenharmony_ci        "Return CurrentKeys with 'darwin' modifications."
5407db96d56Sopenharmony_ci        result = self.GetKeySet(self.CurrentKeys())
5417db96d56Sopenharmony_ci
5427db96d56Sopenharmony_ci        if sys.platform == "darwin":
5437db96d56Sopenharmony_ci            # macOS (OS X) Tk variants do not support the "Alt"
5447db96d56Sopenharmony_ci            # keyboard modifier.  Replace it with "Option".
5457db96d56Sopenharmony_ci            # TODO (Ned?): the "Option" modifier does not work properly
5467db96d56Sopenharmony_ci            #     for Cocoa Tk and XQuartz Tk so we should not use it
5477db96d56Sopenharmony_ci            #     in the default 'OSX' keyset.
5487db96d56Sopenharmony_ci            for k, v in result.items():
5497db96d56Sopenharmony_ci                v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
5507db96d56Sopenharmony_ci                if v != v2:
5517db96d56Sopenharmony_ci                    result[k] = v2
5527db96d56Sopenharmony_ci
5537db96d56Sopenharmony_ci        return result
5547db96d56Sopenharmony_ci
5557db96d56Sopenharmony_ci    def GetKeySet(self, keySetName):
5567db96d56Sopenharmony_ci        """Return event-key dict for keySetName core plus active extensions.
5577db96d56Sopenharmony_ci
5587db96d56Sopenharmony_ci        If a binding defined in an extension is already in use, the
5597db96d56Sopenharmony_ci        extension binding is disabled by being set to ''
5607db96d56Sopenharmony_ci        """
5617db96d56Sopenharmony_ci        keySet = self.GetCoreKeys(keySetName)
5627db96d56Sopenharmony_ci        activeExtns = self.GetExtensions(active_only=1)
5637db96d56Sopenharmony_ci        for extn in activeExtns:
5647db96d56Sopenharmony_ci            extKeys = self.__GetRawExtensionKeys(extn)
5657db96d56Sopenharmony_ci            if extKeys: #the extension defines keybindings
5667db96d56Sopenharmony_ci                for event in extKeys:
5677db96d56Sopenharmony_ci                    if extKeys[event] in keySet.values():
5687db96d56Sopenharmony_ci                        #the binding is already in use
5697db96d56Sopenharmony_ci                        extKeys[event] = '' #disable this binding
5707db96d56Sopenharmony_ci                    keySet[event] = extKeys[event] #add binding
5717db96d56Sopenharmony_ci        return keySet
5727db96d56Sopenharmony_ci
5737db96d56Sopenharmony_ci    def IsCoreBinding(self, virtualEvent):
5747db96d56Sopenharmony_ci        """Return True if the virtual event is one of the core idle key events.
5757db96d56Sopenharmony_ci
5767db96d56Sopenharmony_ci        virtualEvent - string, name of the virtual event to test for,
5777db96d56Sopenharmony_ci                       without the enclosing '<< >>'
5787db96d56Sopenharmony_ci        """
5797db96d56Sopenharmony_ci        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
5807db96d56Sopenharmony_ci
5817db96d56Sopenharmony_ci# TODO make keyBindings a file or class attribute used for test above
5827db96d56Sopenharmony_ci# and copied in function below.
5837db96d56Sopenharmony_ci
5847db96d56Sopenharmony_ci    former_extension_events = {  #  Those with user-configurable keys.
5857db96d56Sopenharmony_ci        '<<force-open-completions>>', '<<expand-word>>',
5867db96d56Sopenharmony_ci        '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
5877db96d56Sopenharmony_ci         '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
5887db96d56Sopenharmony_ci         '<<run-custom>>',
5897db96d56Sopenharmony_ci         }
5907db96d56Sopenharmony_ci
5917db96d56Sopenharmony_ci    def GetCoreKeys(self, keySetName=None):
5927db96d56Sopenharmony_ci        """Return dict of core virtual-key keybindings for keySetName.
5937db96d56Sopenharmony_ci
5947db96d56Sopenharmony_ci        The default keySetName None corresponds to the keyBindings base
5957db96d56Sopenharmony_ci        dict. If keySetName is not None, bindings from the config
5967db96d56Sopenharmony_ci        file(s) are loaded _over_ these defaults, so if there is a
5977db96d56Sopenharmony_ci        problem getting any core binding there will be an 'ultimate last
5987db96d56Sopenharmony_ci        resort fallback' to the CUA-ish bindings defined here.
5997db96d56Sopenharmony_ci        """
6007db96d56Sopenharmony_ci        keyBindings={
6017db96d56Sopenharmony_ci            '<<copy>>': ['<Control-c>', '<Control-C>'],
6027db96d56Sopenharmony_ci            '<<cut>>': ['<Control-x>', '<Control-X>'],
6037db96d56Sopenharmony_ci            '<<paste>>': ['<Control-v>', '<Control-V>'],
6047db96d56Sopenharmony_ci            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
6057db96d56Sopenharmony_ci            '<<center-insert>>': ['<Control-l>'],
6067db96d56Sopenharmony_ci            '<<close-all-windows>>': ['<Control-q>'],
6077db96d56Sopenharmony_ci            '<<close-window>>': ['<Alt-F4>'],
6087db96d56Sopenharmony_ci            '<<do-nothing>>': ['<Control-x>'],
6097db96d56Sopenharmony_ci            '<<end-of-file>>': ['<Control-d>'],
6107db96d56Sopenharmony_ci            '<<python-docs>>': ['<F1>'],
6117db96d56Sopenharmony_ci            '<<python-context-help>>': ['<Shift-F1>'],
6127db96d56Sopenharmony_ci            '<<history-next>>': ['<Alt-n>'],
6137db96d56Sopenharmony_ci            '<<history-previous>>': ['<Alt-p>'],
6147db96d56Sopenharmony_ci            '<<interrupt-execution>>': ['<Control-c>'],
6157db96d56Sopenharmony_ci            '<<view-restart>>': ['<F6>'],
6167db96d56Sopenharmony_ci            '<<restart-shell>>': ['<Control-F6>'],
6177db96d56Sopenharmony_ci            '<<open-class-browser>>': ['<Alt-c>'],
6187db96d56Sopenharmony_ci            '<<open-module>>': ['<Alt-m>'],
6197db96d56Sopenharmony_ci            '<<open-new-window>>': ['<Control-n>'],
6207db96d56Sopenharmony_ci            '<<open-window-from-file>>': ['<Control-o>'],
6217db96d56Sopenharmony_ci            '<<plain-newline-and-indent>>': ['<Control-j>'],
6227db96d56Sopenharmony_ci            '<<print-window>>': ['<Control-p>'],
6237db96d56Sopenharmony_ci            '<<redo>>': ['<Control-y>'],
6247db96d56Sopenharmony_ci            '<<remove-selection>>': ['<Escape>'],
6257db96d56Sopenharmony_ci            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
6267db96d56Sopenharmony_ci            '<<save-window-as-file>>': ['<Alt-s>'],
6277db96d56Sopenharmony_ci            '<<save-window>>': ['<Control-s>'],
6287db96d56Sopenharmony_ci            '<<select-all>>': ['<Alt-a>'],
6297db96d56Sopenharmony_ci            '<<toggle-auto-coloring>>': ['<Control-slash>'],
6307db96d56Sopenharmony_ci            '<<undo>>': ['<Control-z>'],
6317db96d56Sopenharmony_ci            '<<find-again>>': ['<Control-g>', '<F3>'],
6327db96d56Sopenharmony_ci            '<<find-in-files>>': ['<Alt-F3>'],
6337db96d56Sopenharmony_ci            '<<find-selection>>': ['<Control-F3>'],
6347db96d56Sopenharmony_ci            '<<find>>': ['<Control-f>'],
6357db96d56Sopenharmony_ci            '<<replace>>': ['<Control-h>'],
6367db96d56Sopenharmony_ci            '<<goto-line>>': ['<Alt-g>'],
6377db96d56Sopenharmony_ci            '<<smart-backspace>>': ['<Key-BackSpace>'],
6387db96d56Sopenharmony_ci            '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
6397db96d56Sopenharmony_ci            '<<smart-indent>>': ['<Key-Tab>'],
6407db96d56Sopenharmony_ci            '<<indent-region>>': ['<Control-Key-bracketright>'],
6417db96d56Sopenharmony_ci            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
6427db96d56Sopenharmony_ci            '<<comment-region>>': ['<Alt-Key-3>'],
6437db96d56Sopenharmony_ci            '<<uncomment-region>>': ['<Alt-Key-4>'],
6447db96d56Sopenharmony_ci            '<<tabify-region>>': ['<Alt-Key-5>'],
6457db96d56Sopenharmony_ci            '<<untabify-region>>': ['<Alt-Key-6>'],
6467db96d56Sopenharmony_ci            '<<toggle-tabs>>': ['<Alt-Key-t>'],
6477db96d56Sopenharmony_ci            '<<change-indentwidth>>': ['<Alt-Key-u>'],
6487db96d56Sopenharmony_ci            '<<del-word-left>>': ['<Control-Key-BackSpace>'],
6497db96d56Sopenharmony_ci            '<<del-word-right>>': ['<Control-Key-Delete>'],
6507db96d56Sopenharmony_ci            '<<force-open-completions>>': ['<Control-Key-space>'],
6517db96d56Sopenharmony_ci            '<<expand-word>>': ['<Alt-Key-slash>'],
6527db96d56Sopenharmony_ci            '<<force-open-calltip>>': ['<Control-Key-backslash>'],
6537db96d56Sopenharmony_ci            '<<flash-paren>>': ['<Control-Key-0>'],
6547db96d56Sopenharmony_ci            '<<format-paragraph>>': ['<Alt-Key-q>'],
6557db96d56Sopenharmony_ci            '<<run-module>>': ['<Key-F5>'],
6567db96d56Sopenharmony_ci            '<<run-custom>>': ['<Shift-Key-F5>'],
6577db96d56Sopenharmony_ci            '<<check-module>>': ['<Alt-Key-x>'],
6587db96d56Sopenharmony_ci            '<<zoom-height>>': ['<Alt-Key-2>'],
6597db96d56Sopenharmony_ci            }
6607db96d56Sopenharmony_ci
6617db96d56Sopenharmony_ci        if keySetName:
6627db96d56Sopenharmony_ci            if not (self.userCfg['keys'].has_section(keySetName) or
6637db96d56Sopenharmony_ci                    self.defaultCfg['keys'].has_section(keySetName)):
6647db96d56Sopenharmony_ci                warning = (
6657db96d56Sopenharmony_ci                    '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
6667db96d56Sopenharmony_ci                    ' key set %r is not defined, using default bindings.' %
6677db96d56Sopenharmony_ci                    (keySetName,)
6687db96d56Sopenharmony_ci                )
6697db96d56Sopenharmony_ci                _warn(warning, 'keys', keySetName)
6707db96d56Sopenharmony_ci            else:
6717db96d56Sopenharmony_ci                for event in keyBindings:
6727db96d56Sopenharmony_ci                    binding = self.GetKeyBinding(keySetName, event)
6737db96d56Sopenharmony_ci                    if binding:
6747db96d56Sopenharmony_ci                        keyBindings[event] = binding
6757db96d56Sopenharmony_ci                    # Otherwise return default in keyBindings.
6767db96d56Sopenharmony_ci                    elif event not in self.former_extension_events:
6777db96d56Sopenharmony_ci                        warning = (
6787db96d56Sopenharmony_ci                            '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
6797db96d56Sopenharmony_ci                            ' problem retrieving key binding for event %r\n'
6807db96d56Sopenharmony_ci                            ' from key set %r.\n'
6817db96d56Sopenharmony_ci                            ' returning default value: %r' %
6827db96d56Sopenharmony_ci                            (event, keySetName, keyBindings[event])
6837db96d56Sopenharmony_ci                        )
6847db96d56Sopenharmony_ci                        _warn(warning, 'keys', keySetName, event)
6857db96d56Sopenharmony_ci        return keyBindings
6867db96d56Sopenharmony_ci
6877db96d56Sopenharmony_ci    def GetExtraHelpSourceList(self, configSet):
6887db96d56Sopenharmony_ci        """Return list of extra help sources from a given configSet.
6897db96d56Sopenharmony_ci
6907db96d56Sopenharmony_ci        Valid configSets are 'user' or 'default'.  Return a list of tuples of
6917db96d56Sopenharmony_ci        the form (menu_item , path_to_help_file , option), or return the empty
6927db96d56Sopenharmony_ci        list.  'option' is the sequence number of the help resource.  'option'
6937db96d56Sopenharmony_ci        values determine the position of the menu items on the Help menu,
6947db96d56Sopenharmony_ci        therefore the returned list must be sorted by 'option'.
6957db96d56Sopenharmony_ci
6967db96d56Sopenharmony_ci        """
6977db96d56Sopenharmony_ci        helpSources = []
6987db96d56Sopenharmony_ci        if configSet == 'user':
6997db96d56Sopenharmony_ci            cfgParser = self.userCfg['main']
7007db96d56Sopenharmony_ci        elif configSet == 'default':
7017db96d56Sopenharmony_ci            cfgParser = self.defaultCfg['main']
7027db96d56Sopenharmony_ci        else:
7037db96d56Sopenharmony_ci            raise InvalidConfigSet('Invalid configSet specified')
7047db96d56Sopenharmony_ci        options=cfgParser.GetOptionList('HelpFiles')
7057db96d56Sopenharmony_ci        for option in options:
7067db96d56Sopenharmony_ci            value=cfgParser.Get('HelpFiles', option, default=';')
7077db96d56Sopenharmony_ci            if value.find(';') == -1: #malformed config entry with no ';'
7087db96d56Sopenharmony_ci                menuItem = '' #make these empty
7097db96d56Sopenharmony_ci                helpPath = '' #so value won't be added to list
7107db96d56Sopenharmony_ci            else: #config entry contains ';' as expected
7117db96d56Sopenharmony_ci                value=value.split(';')
7127db96d56Sopenharmony_ci                menuItem=value[0].strip()
7137db96d56Sopenharmony_ci                helpPath=value[1].strip()
7147db96d56Sopenharmony_ci            if menuItem and helpPath: #neither are empty strings
7157db96d56Sopenharmony_ci                helpSources.append( (menuItem,helpPath,option) )
7167db96d56Sopenharmony_ci        helpSources.sort(key=lambda x: x[2])
7177db96d56Sopenharmony_ci        return helpSources
7187db96d56Sopenharmony_ci
7197db96d56Sopenharmony_ci    def GetAllExtraHelpSourcesList(self):
7207db96d56Sopenharmony_ci        """Return a list of the details of all additional help sources.
7217db96d56Sopenharmony_ci
7227db96d56Sopenharmony_ci        Tuples in the list are those of GetExtraHelpSourceList.
7237db96d56Sopenharmony_ci        """
7247db96d56Sopenharmony_ci        allHelpSources = (self.GetExtraHelpSourceList('default') +
7257db96d56Sopenharmony_ci                self.GetExtraHelpSourceList('user') )
7267db96d56Sopenharmony_ci        return allHelpSources
7277db96d56Sopenharmony_ci
7287db96d56Sopenharmony_ci    def GetFont(self, root, configType, section):
7297db96d56Sopenharmony_ci        """Retrieve a font from configuration (font, font-size, font-bold)
7307db96d56Sopenharmony_ci        Intercept the special value 'TkFixedFont' and substitute
7317db96d56Sopenharmony_ci        the actual font, factoring in some tweaks if needed for
7327db96d56Sopenharmony_ci        appearance sakes.
7337db96d56Sopenharmony_ci
7347db96d56Sopenharmony_ci        The 'root' parameter can normally be any valid Tkinter widget.
7357db96d56Sopenharmony_ci
7367db96d56Sopenharmony_ci        Return a tuple (family, size, weight) suitable for passing
7377db96d56Sopenharmony_ci        to tkinter.Font
7387db96d56Sopenharmony_ci        """
7397db96d56Sopenharmony_ci        family = self.GetOption(configType, section, 'font', default='courier')
7407db96d56Sopenharmony_ci        size = self.GetOption(configType, section, 'font-size', type='int',
7417db96d56Sopenharmony_ci                              default='10')
7427db96d56Sopenharmony_ci        bold = self.GetOption(configType, section, 'font-bold', default=0,
7437db96d56Sopenharmony_ci                              type='bool')
7447db96d56Sopenharmony_ci        if (family == 'TkFixedFont'):
7457db96d56Sopenharmony_ci            f = Font(name='TkFixedFont', exists=True, root=root)
7467db96d56Sopenharmony_ci            actualFont = Font.actual(f)
7477db96d56Sopenharmony_ci            family = actualFont['family']
7487db96d56Sopenharmony_ci            size = actualFont['size']
7497db96d56Sopenharmony_ci            if size <= 0:
7507db96d56Sopenharmony_ci                size = 10  # if font in pixels, ignore actual size
7517db96d56Sopenharmony_ci            bold = actualFont['weight'] == 'bold'
7527db96d56Sopenharmony_ci        return (family, size, 'bold' if bold else 'normal')
7537db96d56Sopenharmony_ci
7547db96d56Sopenharmony_ci    def LoadCfgFiles(self):
7557db96d56Sopenharmony_ci        "Load all configuration files."
7567db96d56Sopenharmony_ci        for key in self.defaultCfg:
7577db96d56Sopenharmony_ci            self.defaultCfg[key].Load()
7587db96d56Sopenharmony_ci            self.userCfg[key].Load() #same keys
7597db96d56Sopenharmony_ci
7607db96d56Sopenharmony_ci    def SaveUserCfgFiles(self):
7617db96d56Sopenharmony_ci        "Write all loaded user configuration files to disk."
7627db96d56Sopenharmony_ci        for key in self.userCfg:
7637db96d56Sopenharmony_ci            self.userCfg[key].Save()
7647db96d56Sopenharmony_ci
7657db96d56Sopenharmony_ci
7667db96d56Sopenharmony_ciidleConf = IdleConf()
7677db96d56Sopenharmony_ci
7687db96d56Sopenharmony_ci_warned = set()
7697db96d56Sopenharmony_cidef _warn(msg, *key):
7707db96d56Sopenharmony_ci    key = (msg,) + key
7717db96d56Sopenharmony_ci    if key not in _warned:
7727db96d56Sopenharmony_ci        try:
7737db96d56Sopenharmony_ci            print(msg, file=sys.stderr)
7747db96d56Sopenharmony_ci        except OSError:
7757db96d56Sopenharmony_ci            pass
7767db96d56Sopenharmony_ci        _warned.add(key)
7777db96d56Sopenharmony_ci
7787db96d56Sopenharmony_ci
7797db96d56Sopenharmony_ciclass ConfigChanges(dict):
7807db96d56Sopenharmony_ci    """Manage a user's proposed configuration option changes.
7817db96d56Sopenharmony_ci
7827db96d56Sopenharmony_ci    Names used across multiple methods:
7837db96d56Sopenharmony_ci        page -- one of the 4 top-level dicts representing a
7847db96d56Sopenharmony_ci                .idlerc/config-x.cfg file.
7857db96d56Sopenharmony_ci        config_type -- name of a page.
7867db96d56Sopenharmony_ci        section -- a section within a page/file.
7877db96d56Sopenharmony_ci        option -- name of an option within a section.
7887db96d56Sopenharmony_ci        value -- value for the option.
7897db96d56Sopenharmony_ci
7907db96d56Sopenharmony_ci    Methods
7917db96d56Sopenharmony_ci        add_option: Add option and value to changes.
7927db96d56Sopenharmony_ci        save_option: Save option and value to config parser.
7937db96d56Sopenharmony_ci        save_all: Save all the changes to the config parser and file.
7947db96d56Sopenharmony_ci        delete_section: If section exists,
7957db96d56Sopenharmony_ci                        delete from changes, userCfg, and file.
7967db96d56Sopenharmony_ci        clear: Clear all changes by clearing each page.
7977db96d56Sopenharmony_ci    """
7987db96d56Sopenharmony_ci    def __init__(self):
7997db96d56Sopenharmony_ci        "Create a page for each configuration file"
8007db96d56Sopenharmony_ci        self.pages = []  # List of unhashable dicts.
8017db96d56Sopenharmony_ci        for config_type in idleConf.config_types:
8027db96d56Sopenharmony_ci            self[config_type] = {}
8037db96d56Sopenharmony_ci            self.pages.append(self[config_type])
8047db96d56Sopenharmony_ci
8057db96d56Sopenharmony_ci    def add_option(self, config_type, section, item, value):
8067db96d56Sopenharmony_ci        "Add item/value pair for config_type and section."
8077db96d56Sopenharmony_ci        page = self[config_type]
8087db96d56Sopenharmony_ci        value = str(value)  # Make sure we use a string.
8097db96d56Sopenharmony_ci        if section not in page:
8107db96d56Sopenharmony_ci            page[section] = {}
8117db96d56Sopenharmony_ci        page[section][item] = value
8127db96d56Sopenharmony_ci
8137db96d56Sopenharmony_ci    @staticmethod
8147db96d56Sopenharmony_ci    def save_option(config_type, section, item, value):
8157db96d56Sopenharmony_ci        """Return True if the configuration value was added or changed.
8167db96d56Sopenharmony_ci
8177db96d56Sopenharmony_ci        Helper for save_all.
8187db96d56Sopenharmony_ci        """
8197db96d56Sopenharmony_ci        if idleConf.defaultCfg[config_type].has_option(section, item):
8207db96d56Sopenharmony_ci            if idleConf.defaultCfg[config_type].Get(section, item) == value:
8217db96d56Sopenharmony_ci                # The setting equals a default setting, remove it from user cfg.
8227db96d56Sopenharmony_ci                return idleConf.userCfg[config_type].RemoveOption(section, item)
8237db96d56Sopenharmony_ci        # If we got here, set the option.
8247db96d56Sopenharmony_ci        return idleConf.userCfg[config_type].SetOption(section, item, value)
8257db96d56Sopenharmony_ci
8267db96d56Sopenharmony_ci    def save_all(self):
8277db96d56Sopenharmony_ci        """Save configuration changes to the user config file.
8287db96d56Sopenharmony_ci
8297db96d56Sopenharmony_ci        Clear self in preparation for additional changes.
8307db96d56Sopenharmony_ci        Return changed for testing.
8317db96d56Sopenharmony_ci        """
8327db96d56Sopenharmony_ci        idleConf.userCfg['main'].Save()
8337db96d56Sopenharmony_ci
8347db96d56Sopenharmony_ci        changed = False
8357db96d56Sopenharmony_ci        for config_type in self:
8367db96d56Sopenharmony_ci            cfg_type_changed = False
8377db96d56Sopenharmony_ci            page = self[config_type]
8387db96d56Sopenharmony_ci            for section in page:
8397db96d56Sopenharmony_ci                if section == 'HelpFiles':  # Remove it for replacement.
8407db96d56Sopenharmony_ci                    idleConf.userCfg['main'].remove_section('HelpFiles')
8417db96d56Sopenharmony_ci                    cfg_type_changed = True
8427db96d56Sopenharmony_ci                for item, value in page[section].items():
8437db96d56Sopenharmony_ci                    if self.save_option(config_type, section, item, value):
8447db96d56Sopenharmony_ci                        cfg_type_changed = True
8457db96d56Sopenharmony_ci            if cfg_type_changed:
8467db96d56Sopenharmony_ci                idleConf.userCfg[config_type].Save()
8477db96d56Sopenharmony_ci                changed = True
8487db96d56Sopenharmony_ci        for config_type in ['keys', 'highlight']:
8497db96d56Sopenharmony_ci            # Save these even if unchanged!
8507db96d56Sopenharmony_ci            idleConf.userCfg[config_type].Save()
8517db96d56Sopenharmony_ci        self.clear()
8527db96d56Sopenharmony_ci        # ConfigDialog caller must add the following call
8537db96d56Sopenharmony_ci        # self.save_all_changed_extensions()  # Uses a different mechanism.
8547db96d56Sopenharmony_ci        return changed
8557db96d56Sopenharmony_ci
8567db96d56Sopenharmony_ci    def delete_section(self, config_type, section):
8577db96d56Sopenharmony_ci        """Delete a section from self, userCfg, and file.
8587db96d56Sopenharmony_ci
8597db96d56Sopenharmony_ci        Used to delete custom themes and keysets.
8607db96d56Sopenharmony_ci        """
8617db96d56Sopenharmony_ci        if section in self[config_type]:
8627db96d56Sopenharmony_ci            del self[config_type][section]
8637db96d56Sopenharmony_ci        configpage = idleConf.userCfg[config_type]
8647db96d56Sopenharmony_ci        configpage.remove_section(section)
8657db96d56Sopenharmony_ci        configpage.Save()
8667db96d56Sopenharmony_ci
8677db96d56Sopenharmony_ci    def clear(self):
8687db96d56Sopenharmony_ci        """Clear all 4 pages.
8697db96d56Sopenharmony_ci
8707db96d56Sopenharmony_ci        Called in save_all after saving to idleConf.
8717db96d56Sopenharmony_ci        XXX Mark window *title* when there are changes; unmark here.
8727db96d56Sopenharmony_ci        """
8737db96d56Sopenharmony_ci        for page in self.pages:
8747db96d56Sopenharmony_ci            page.clear()
8757db96d56Sopenharmony_ci
8767db96d56Sopenharmony_ci
8777db96d56Sopenharmony_ci# TODO Revise test output, write expanded unittest
8787db96d56Sopenharmony_cidef _dump():  # htest # (not really, but ignore in coverage)
8797db96d56Sopenharmony_ci    from zlib import crc32
8807db96d56Sopenharmony_ci    line, crc = 0, 0
8817db96d56Sopenharmony_ci
8827db96d56Sopenharmony_ci    def sprint(obj):
8837db96d56Sopenharmony_ci        global line, crc
8847db96d56Sopenharmony_ci        txt = str(obj)
8857db96d56Sopenharmony_ci        line += 1
8867db96d56Sopenharmony_ci        crc = crc32(txt.encode(encoding='utf-8'), crc)
8877db96d56Sopenharmony_ci        print(txt)
8887db96d56Sopenharmony_ci        #print('***', line, crc, '***')  # Uncomment for diagnosis.
8897db96d56Sopenharmony_ci
8907db96d56Sopenharmony_ci    def dumpCfg(cfg):
8917db96d56Sopenharmony_ci        print('\n', cfg, '\n')  # Cfg has variable '0xnnnnnnnn' address.
8927db96d56Sopenharmony_ci        for key in sorted(cfg.keys()):
8937db96d56Sopenharmony_ci            sections = cfg[key].sections()
8947db96d56Sopenharmony_ci            sprint(key)
8957db96d56Sopenharmony_ci            sprint(sections)
8967db96d56Sopenharmony_ci            for section in sections:
8977db96d56Sopenharmony_ci                options = cfg[key].options(section)
8987db96d56Sopenharmony_ci                sprint(section)
8997db96d56Sopenharmony_ci                sprint(options)
9007db96d56Sopenharmony_ci                for option in options:
9017db96d56Sopenharmony_ci                    sprint(option + ' = ' + cfg[key].Get(section, option))
9027db96d56Sopenharmony_ci
9037db96d56Sopenharmony_ci    dumpCfg(idleConf.defaultCfg)
9047db96d56Sopenharmony_ci    dumpCfg(idleConf.userCfg)
9057db96d56Sopenharmony_ci    print('\nlines = ', line, ', crc = ', crc, sep='')
9067db96d56Sopenharmony_ci
9077db96d56Sopenharmony_ciif __name__ == '__main__':
9087db96d56Sopenharmony_ci    from unittest import main
9097db96d56Sopenharmony_ci    main('idlelib.idle_test.test_config', verbosity=2, exit=False)
9107db96d56Sopenharmony_ci
9117db96d56Sopenharmony_ci    # Run revised _dump() as htest?
912